mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 12:28:01 +08:00
feat(repodb): add user related information to repodb (#1317)
Initial code was contributed by Bogdan BIVOLARU <104334+bogdanbiv@users.noreply.github.com> Moved implementation from a separate db to repodb by Andrei Aaron <aaaron@luxoft.com> Not done yet: - run/test dynamodb implementation, only boltdb was tested - add additional coverage for existing functionality - add web-based APIs to toggle the stars/bookmarks on/off Initially graphql mutation was discussed for the missing API but we decided REST endpoints would be better suited for configuration feat(userdb): complete functionality for userdb integration - dynamodb rollback changes to user starred repos in case increasing the total star count fails - dynamodb increment/decrement repostars in repometa when user stars/unstars a repo - dynamodb check anonymous user permissions are working as intendend - common test handle anonymous users - RepoMeta2RepoSummary set IsStarred and IsBookmarked feat(userdb): rest api calls for toggling stars/bookmarks on/off test(userdb): blackbox tests test(userdb): move preferences tests in a different file with specific build tags feat(repodb): add is-starred and is-bookmarked fields to repo-meta - removed duplicated logic for determining if a repo is starred/bookmarked Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com> Co-authored-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
//go:build userprefs
|
||||
// +build userprefs
|
||||
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
)
|
||||
|
||||
const (
|
||||
ToggleRepoBookmarkAction = "toggleBookmark"
|
||||
ToggleRepoStarAction = "toggleStar"
|
||||
)
|
||||
|
||||
func SetupUserPreferencesRoutes(config *config.Config, router *mux.Router, storeController storage.StoreController,
|
||||
repoDB repodb.RepoDB, cveInfo CveInfo, log log.Logger,
|
||||
) {
|
||||
if config.Extensions.Search != nil && *config.Extensions.Search.Enable {
|
||||
log.Info().Msg("setting up user preferences routes")
|
||||
|
||||
userprefsRouter := router.PathPrefix(constants.ExtUserPreferencesPrefix).Subrouter()
|
||||
|
||||
userprefsRouter.HandleFunc("", HandleUserPrefs(repoDB, log)).Methods(http.MethodPut)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleUserPrefs(repoDB repodb.RepoDB, log log.Logger) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(rsp http.ResponseWriter, req *http.Request) {
|
||||
if !queryHasParams(req.URL.Query(), []string{"action"}) {
|
||||
rsp.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
action := req.URL.Query().Get("action")
|
||||
|
||||
switch action {
|
||||
case ToggleRepoBookmarkAction:
|
||||
PutBookmark(rsp, req, repoDB, log) //nolint:contextcheck
|
||||
|
||||
return
|
||||
case ToggleRepoStarAction:
|
||||
PutStar(rsp, req, repoDB, log) //nolint:contextcheck
|
||||
|
||||
return
|
||||
default:
|
||||
rsp.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PutStar(rsp http.ResponseWriter, req *http.Request, repoDB repodb.RepoDB, log log.Logger) {
|
||||
if !queryHasParams(req.URL.Query(), []string{"repo"}) {
|
||||
rsp.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
repo := req.URL.Query().Get("repo")
|
||||
|
||||
if repo == "" {
|
||||
rsp.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_, err := repoDB.ToggleStarRepo(req.Context(), repo)
|
||||
if err != nil {
|
||||
if errors.Is(err, zerr.ErrRepoMetaNotFound) {
|
||||
rsp.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
} else if errors.Is(err, zerr.ErrUserDataNotAllowed) {
|
||||
rsp.WriteHeader(http.StatusForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rsp.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rsp.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func PutBookmark(rsp http.ResponseWriter, req *http.Request, repoDB repodb.RepoDB, log log.Logger) {
|
||||
if !queryHasParams(req.URL.Query(), []string{"repo"}) {
|
||||
rsp.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
repo := req.URL.Query().Get("repo")
|
||||
|
||||
if repo == "" {
|
||||
rsp.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_, err := repoDB.ToggleBookmarkRepo(req.Context(), repo)
|
||||
if err != nil {
|
||||
if errors.Is(err, zerr.ErrRepoMetaNotFound) {
|
||||
rsp.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
} else if errors.Is(err, zerr.ErrUserDataNotAllowed) {
|
||||
rsp.WriteHeader(http.StatusForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rsp.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rsp.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func queryHasParams(values url.Values, params []string) bool {
|
||||
for _, param := range params {
|
||||
if !values.Has(param) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//go:build !userprefs
|
||||
// +build !userprefs
|
||||
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
)
|
||||
|
||||
func SetupUserPreferencesRoutes(config *config.Config, router *mux.Router, storeController storage.StoreController,
|
||||
repoDB repodb.RepoDB, cveInfo CveInfo, log log.Logger,
|
||||
) {
|
||||
log.Warn().Msg("userprefs extension is disabled because given zot binary doesn't" +
|
||||
"include this feature please build a binary that does so")
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
//go:build userprefs
|
||||
// +build userprefs
|
||||
|
||||
package extensions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/extensions"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
|
||||
var ErrTestError = errors.New("TestError")
|
||||
|
||||
const UserprefsBaseURL = "http://127.0.0.1:8080/v2/_zot/ext/userprefs"
|
||||
|
||||
func TestHandlers(t *testing.T) {
|
||||
log := log.NewLogger("debug", "")
|
||||
mockrepoDB := mocks.RepoDBMock{}
|
||||
|
||||
Convey("No repo in request", t, func() {
|
||||
request := httptest.NewRequest("GET", UserprefsBaseURL+"", strings.NewReader("My string"))
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
extensions.PutStar(response, request, mockrepoDB, log)
|
||||
res := response.Result()
|
||||
So(res.StatusCode, ShouldEqual, http.StatusBadRequest)
|
||||
defer res.Body.Close()
|
||||
|
||||
extensions.PutBookmark(response, request, mockrepoDB, log)
|
||||
res = response.Result()
|
||||
So(res.StatusCode, ShouldEqual, http.StatusBadRequest)
|
||||
defer res.Body.Close()
|
||||
})
|
||||
|
||||
Convey("Empty repo in request", t, func() {
|
||||
request := httptest.NewRequest("GET", UserprefsBaseURL+"?repo=", strings.NewReader("My string"))
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
extensions.PutStar(response, request, mockrepoDB, log)
|
||||
res := response.Result()
|
||||
So(res.StatusCode, ShouldEqual, http.StatusNotFound)
|
||||
defer res.Body.Close()
|
||||
|
||||
extensions.PutBookmark(response, request, mockrepoDB, log)
|
||||
res = response.Result()
|
||||
So(res.StatusCode, ShouldEqual, http.StatusNotFound)
|
||||
defer res.Body.Close()
|
||||
})
|
||||
|
||||
Convey("ToggleStarRepo different errors", t, func() {
|
||||
request := httptest.NewRequest("GET", UserprefsBaseURL+"?repo=test",
|
||||
strings.NewReader("My string"))
|
||||
|
||||
Convey("ErrRepoMetaNotFound", func() {
|
||||
mockrepoDB.ToggleStarRepoFn = func(ctx context.Context, repo string) (repodb.ToggleState, error) {
|
||||
return repodb.NotChanged, zerr.ErrRepoMetaNotFound
|
||||
}
|
||||
|
||||
mockrepoDB.ToggleBookmarkRepoFn = func(ctx context.Context, repo string) (repodb.ToggleState, error) {
|
||||
return repodb.NotChanged, zerr.ErrRepoMetaNotFound
|
||||
}
|
||||
|
||||
response := httptest.NewRecorder()
|
||||
extensions.PutBookmark(response, request, mockrepoDB, log)
|
||||
res := response.Result()
|
||||
So(res.StatusCode, ShouldEqual, http.StatusNotFound)
|
||||
defer res.Body.Close()
|
||||
|
||||
response = httptest.NewRecorder()
|
||||
extensions.PutStar(response, request, mockrepoDB, log)
|
||||
res = response.Result()
|
||||
So(res.StatusCode, ShouldEqual, http.StatusNotFound)
|
||||
defer res.Body.Close()
|
||||
})
|
||||
|
||||
Convey("ErrUserDataNotAllowed", func() {
|
||||
request = mux.SetURLVars(request, map[string]string{
|
||||
"name": "repo",
|
||||
})
|
||||
|
||||
mockrepoDB.ToggleBookmarkRepoFn = func(ctx context.Context, repo string) (repodb.ToggleState, error) {
|
||||
return repodb.NotChanged, zerr.ErrUserDataNotAllowed
|
||||
}
|
||||
|
||||
mockrepoDB.ToggleStarRepoFn = func(ctx context.Context, repo string) (repodb.ToggleState, error) {
|
||||
return repodb.NotChanged, zerr.ErrUserDataNotAllowed
|
||||
}
|
||||
|
||||
response := httptest.NewRecorder()
|
||||
extensions.PutBookmark(response, request, mockrepoDB, log)
|
||||
res := response.Result()
|
||||
So(res.StatusCode, ShouldEqual, http.StatusForbidden)
|
||||
defer res.Body.Close()
|
||||
|
||||
response = httptest.NewRecorder()
|
||||
extensions.PutStar(response, request, mockrepoDB, log)
|
||||
res = response.Result()
|
||||
So(res.StatusCode, ShouldEqual, http.StatusForbidden)
|
||||
defer res.Body.Close()
|
||||
})
|
||||
|
||||
Convey("ErrUnexpectedError", func() {
|
||||
request = mux.SetURLVars(request, map[string]string{
|
||||
"name": "repo",
|
||||
})
|
||||
|
||||
mockrepoDB.ToggleBookmarkRepoFn = func(ctx context.Context, repo string) (repodb.ToggleState, error) {
|
||||
return repodb.NotChanged, ErrTestError
|
||||
}
|
||||
|
||||
mockrepoDB.ToggleStarRepoFn = func(ctx context.Context, repo string) (repodb.ToggleState, error) {
|
||||
return repodb.NotChanged, ErrTestError
|
||||
}
|
||||
response := httptest.NewRecorder()
|
||||
extensions.PutBookmark(response, request, mockrepoDB, log)
|
||||
res := response.Result()
|
||||
So(res.StatusCode, ShouldEqual, http.StatusInternalServerError)
|
||||
defer res.Body.Close()
|
||||
|
||||
response = httptest.NewRecorder()
|
||||
extensions.PutStar(response, request, mockrepoDB, log)
|
||||
res = response.Result()
|
||||
So(res.StatusCode, ShouldEqual, http.StatusInternalServerError)
|
||||
defer res.Body.Close()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -31,15 +31,15 @@ func RepoMeta2RepoSummary(ctx context.Context, repoMeta repodb.RepoMetadata,
|
||||
skip SkipQGLField, cveInfo cveinfo.CveInfo,
|
||||
) *gql_generated.RepoSummary {
|
||||
var (
|
||||
repoName = repoMeta.Name
|
||||
repoLastUpdatedTimestamp = time.Time{}
|
||||
repoPlatformsSet = map[string]*gql_generated.Platform{}
|
||||
repoVendorsSet = map[string]bool{}
|
||||
lastUpdatedImageSummary *gql_generated.ImageSummary
|
||||
repoStarCount = repoMeta.Stars
|
||||
isBookmarked = false
|
||||
isStarred = false
|
||||
repoDownloadCount = 0
|
||||
repoName = repoMeta.Name
|
||||
repoStarCount = repoMeta.Stars // total number of stars
|
||||
repoIsUserStarred = repoMeta.IsStarred // value specific to the current user
|
||||
repoIsUserBookMarked = repoMeta.IsBookmarked // value specific to the current user
|
||||
|
||||
// map used to keep track of all blobs of a repo without dublicates as
|
||||
// some images may have the same layers
|
||||
@@ -88,6 +88,7 @@ func RepoMeta2RepoSummary(ctx context.Context, repoMeta repodb.RepoMetadata,
|
||||
repoSize := strconv.FormatInt(size, 10)
|
||||
|
||||
repoPlatforms := make([]*gql_generated.Platform, 0, len(repoPlatformsSet))
|
||||
|
||||
for _, platform := range repoPlatformsSet {
|
||||
repoPlatforms = append(repoPlatforms, platform)
|
||||
}
|
||||
@@ -129,8 +130,8 @@ func RepoMeta2RepoSummary(ctx context.Context, repoMeta repodb.RepoMetadata,
|
||||
NewestImage: lastUpdatedImageSummary,
|
||||
DownloadCount: &repoDownloadCount,
|
||||
StarCount: &repoStarCount,
|
||||
IsBookmarked: &isBookmarked,
|
||||
IsStarred: &isStarred,
|
||||
IsBookmarked: &repoIsUserBookMarked,
|
||||
IsStarred: &repoIsUserStarred,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,15 +575,15 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta repodb.RepoMetadata
|
||||
skip SkipQGLField, cveInfo cveinfo.CveInfo, log log.Logger,
|
||||
) (*gql_generated.RepoSummary, []*gql_generated.ImageSummary) {
|
||||
var (
|
||||
repoName = repoMeta.Name
|
||||
repoLastUpdatedTimestamp = time.Time{}
|
||||
repoPlatformsSet = map[string]*gql_generated.Platform{}
|
||||
repoVendorsSet = map[string]bool{}
|
||||
lastUpdatedImageSummary *gql_generated.ImageSummary
|
||||
repoStarCount = repoMeta.Stars
|
||||
isBookmarked = false
|
||||
isStarred = false
|
||||
repoDownloadCount = 0
|
||||
repoName = repoMeta.Name
|
||||
repoStarCount = repoMeta.Stars // total number of stars
|
||||
isStarred = repoMeta.IsStarred // value specific to the current user
|
||||
isBookmarked = repoMeta.IsBookmarked // value specific to the current user
|
||||
|
||||
// map used to keep track of all blobs of a repo without dublicates as
|
||||
// some images may have the same layers
|
||||
@@ -632,6 +633,7 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta repodb.RepoMetadata
|
||||
repoSize := strconv.FormatInt(size, 10)
|
||||
|
||||
repoPlatforms := make([]*gql_generated.Platform, 0, len(repoPlatformsSet))
|
||||
|
||||
for _, platform := range repoPlatformsSet {
|
||||
repoPlatforms = append(repoPlatforms, platform)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,8 @@ func TestDigestSearchHTTP(t *testing.T) {
|
||||
)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image1.Reference = "0.0.1"
|
||||
const ver001 = "0.0.1"
|
||||
image1.Reference = ver001
|
||||
err = UploadImage(
|
||||
image1,
|
||||
baseURL,
|
||||
@@ -109,7 +110,7 @@ func TestDigestSearchHTTP(t *testing.T) {
|
||||
)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image2.Reference = "0.0.1"
|
||||
image2.Reference = ver001
|
||||
manifestDigest, err := image2.Digest()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
||||
@@ -156,6 +156,7 @@ type ComplexityRoot struct {
|
||||
|
||||
Query struct {
|
||||
BaseImageList func(childComplexity int, image string, digest *string, requestedPage *PageInput) int
|
||||
BookmarkedRepos func(childComplexity int, requestedPage *PageInput) int
|
||||
CVEListForImage func(childComplexity int, image string, requestedPage *PageInput, searchedCve *string) int
|
||||
DerivedImageList func(childComplexity int, image string, digest *string, requestedPage *PageInput) int
|
||||
ExpandedRepoInfo func(childComplexity int, repo string) int
|
||||
@@ -167,6 +168,7 @@ type ComplexityRoot struct {
|
||||
ImageListWithCVEFixed func(childComplexity int, id string, image string, requestedPage *PageInput) int
|
||||
Referrers func(childComplexity int, repo string, digest string, typeArg []string) int
|
||||
RepoListWithNewestImage func(childComplexity int, requestedPage *PageInput) int
|
||||
StarredRepos func(childComplexity int, requestedPage *PageInput) int
|
||||
}
|
||||
|
||||
Referrer struct {
|
||||
@@ -209,6 +211,8 @@ type QueryResolver interface {
|
||||
BaseImageList(ctx context.Context, image string, digest *string, requestedPage *PageInput) (*PaginatedImagesResult, error)
|
||||
Image(ctx context.Context, image string) (*ImageSummary, error)
|
||||
Referrers(ctx context.Context, repo string, digest string, typeArg []string) ([]*Referrer, error)
|
||||
StarredRepos(ctx context.Context, requestedPage *PageInput) (*PaginatedReposResult, error)
|
||||
BookmarkedRepos(ctx context.Context, requestedPage *PageInput) (*PaginatedReposResult, error)
|
||||
}
|
||||
|
||||
type executableSchema struct {
|
||||
@@ -700,6 +704,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Query.BaseImageList(childComplexity, args["image"].(string), args["digest"].(*string), args["requestedPage"].(*PageInput)), true
|
||||
|
||||
case "Query.BookmarkedRepos":
|
||||
if e.complexity.Query.BookmarkedRepos == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Query_BookmarkedRepos_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.BookmarkedRepos(childComplexity, args["requestedPage"].(*PageInput)), true
|
||||
|
||||
case "Query.CVEListForImage":
|
||||
if e.complexity.Query.CVEListForImage == nil {
|
||||
break
|
||||
@@ -832,6 +848,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Query.RepoListWithNewestImage(childComplexity, args["requestedPage"].(*PageInput)), true
|
||||
|
||||
case "Query.StarredRepos":
|
||||
if e.complexity.Query.StarredRepos == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Query_StarredRepos_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.StarredRepos(childComplexity, args["requestedPage"].(*PageInput)), true
|
||||
|
||||
case "Referrer.Annotations":
|
||||
if e.complexity.Referrer.Annotations == nil {
|
||||
break
|
||||
@@ -1688,6 +1716,22 @@ type Query {
|
||||
"Types of artifacts to return in the referrer list"
|
||||
type: [String!]
|
||||
): [Referrer]!
|
||||
|
||||
"""
|
||||
Receive RepoSummaries of repos starred by current user
|
||||
"""
|
||||
StarredRepos(
|
||||
"Sets the parameters of the requested page (how many to include and offset)"
|
||||
requestedPage: PageInput
|
||||
): PaginatedReposResult!
|
||||
|
||||
"""
|
||||
Receive RepoSummaries of repos bookmarked by current user
|
||||
"""
|
||||
BookmarkedRepos(
|
||||
"Sets the parameters of the requested page (how many to include and offset)"
|
||||
requestedPage: PageInput
|
||||
): PaginatedReposResult!
|
||||
}
|
||||
`, BuiltIn: false},
|
||||
}
|
||||
@@ -1730,6 +1774,21 @@ func (ec *executionContext) field_Query_BaseImageList_args(ctx context.Context,
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Query_BookmarkedRepos_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
var arg0 *PageInput
|
||||
if tmp, ok := rawArgs["requestedPage"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requestedPage"))
|
||||
arg0, err = ec.unmarshalOPageInput2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInput(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["requestedPage"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Query_CVEListForImage_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
@@ -2012,6 +2071,21 @@ func (ec *executionContext) field_Query_RepoListWithNewestImage_args(ctx context
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Query_StarredRepos_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
var arg0 *PageInput
|
||||
if tmp, ok := rawArgs["requestedPage"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requestedPage"))
|
||||
arg0, err = ec.unmarshalOPageInput2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInput(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["requestedPage"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
@@ -5831,6 +5905,128 @@ func (ec *executionContext) fieldContext_Query_Referrers(ctx context.Context, fi
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Query_StarredRepos(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Query_StarredRepos(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Query().StarredRepos(rctx, fc.Args["requestedPage"].(*PageInput))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*PaginatedReposResult)
|
||||
fc.Result = res
|
||||
return ec.marshalNPaginatedReposResult2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPaginatedReposResult(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Query_StarredRepos(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Query",
|
||||
Field: field,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "Page":
|
||||
return ec.fieldContext_PaginatedReposResult_Page(ctx, field)
|
||||
case "Results":
|
||||
return ec.fieldContext_PaginatedReposResult_Results(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type PaginatedReposResult", field.Name)
|
||||
},
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = ec.Recover(ctx, r)
|
||||
ec.Error(ctx, err)
|
||||
}
|
||||
}()
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
if fc.Args, err = ec.field_Query_StarredRepos_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Query_BookmarkedRepos(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Query_BookmarkedRepos(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Query().BookmarkedRepos(rctx, fc.Args["requestedPage"].(*PageInput))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*PaginatedReposResult)
|
||||
fc.Result = res
|
||||
return ec.marshalNPaginatedReposResult2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPaginatedReposResult(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Query_BookmarkedRepos(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Query",
|
||||
Field: field,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "Page":
|
||||
return ec.fieldContext_PaginatedReposResult_Page(ctx, field)
|
||||
case "Results":
|
||||
return ec.fieldContext_PaginatedReposResult_Results(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type PaginatedReposResult", field.Name)
|
||||
},
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = ec.Recover(ctx, r)
|
||||
ec.Error(ctx, err)
|
||||
}
|
||||
}()
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
if fc.Args, err = ec.field_Query_BookmarkedRepos_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Query___type(ctx, field)
|
||||
if err != nil {
|
||||
@@ -9526,6 +9722,52 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
||||
return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc)
|
||||
}
|
||||
|
||||
out.Concurrently(i, func() graphql.Marshaler {
|
||||
return rrm(innerCtx)
|
||||
})
|
||||
case "StarredRepos":
|
||||
field := field
|
||||
|
||||
innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
}
|
||||
}()
|
||||
res = ec._Query_StarredRepos(ctx, field)
|
||||
if res == graphql.Null {
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
rrm := func(ctx context.Context) graphql.Marshaler {
|
||||
return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc)
|
||||
}
|
||||
|
||||
out.Concurrently(i, func() graphql.Marshaler {
|
||||
return rrm(innerCtx)
|
||||
})
|
||||
case "BookmarkedRepos":
|
||||
field := field
|
||||
|
||||
innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
}
|
||||
}()
|
||||
res = ec._Query_BookmarkedRepos(ctx, field)
|
||||
if res == graphql.Null {
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
rrm := func(ctx context.Context) graphql.Marshaler {
|
||||
return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc)
|
||||
}
|
||||
|
||||
out.Concurrently(i, func() graphql.Marshaler {
|
||||
return rrm(innerCtx)
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
"zotregistry.io/zot/pkg/extensions/search/convert"
|
||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||
@@ -300,7 +300,7 @@ func getCVEListForImage(
|
||||
),
|
||||
}
|
||||
|
||||
repo, ref, isTag := common.GetImageDirAndReference(image)
|
||||
repo, ref, isTag := zcommon.GetImageDirAndReference(image)
|
||||
|
||||
if ref == "" {
|
||||
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
|
||||
@@ -560,6 +560,91 @@ func repoListWithNewestImage(
|
||||
return paginatedRepos, nil
|
||||
}
|
||||
|
||||
func getBookmarkedRepos(
|
||||
ctx context.Context,
|
||||
cveInfo cveinfo.CveInfo,
|
||||
log log.Logger, //nolint:unparam // may be used by devs for debugging
|
||||
requestedPage *gql_generated.PageInput,
|
||||
repoDB repodb.RepoDB,
|
||||
) (*gql_generated.PaginatedReposResult, error) {
|
||||
repoNames, err := repoDB.GetBookmarkedRepos(ctx)
|
||||
if err != nil {
|
||||
return &gql_generated.PaginatedReposResult{}, err
|
||||
}
|
||||
|
||||
filterFn := func(repoMeta repodb.RepoMetadata) bool {
|
||||
return zcommon.Contains(repoNames, repoMeta.Name)
|
||||
}
|
||||
|
||||
return getFilteredPaginatedRepos(ctx, cveInfo, filterFn, log, requestedPage, repoDB)
|
||||
}
|
||||
|
||||
func getStarredRepos(
|
||||
ctx context.Context,
|
||||
cveInfo cveinfo.CveInfo,
|
||||
log log.Logger, //nolint:unparam // may be used by devs for debugging
|
||||
requestedPage *gql_generated.PageInput,
|
||||
repoDB repodb.RepoDB,
|
||||
) (*gql_generated.PaginatedReposResult, error) {
|
||||
repoNames, err := repoDB.GetStarredRepos(ctx)
|
||||
if err != nil {
|
||||
return &gql_generated.PaginatedReposResult{}, err
|
||||
}
|
||||
|
||||
filterFn := func(repoMeta repodb.RepoMetadata) bool {
|
||||
return zcommon.Contains(repoNames, repoMeta.Name)
|
||||
}
|
||||
|
||||
return getFilteredPaginatedRepos(ctx, cveInfo, filterFn, log, requestedPage, repoDB)
|
||||
}
|
||||
|
||||
func getFilteredPaginatedRepos(
|
||||
ctx context.Context,
|
||||
cveInfo cveinfo.CveInfo,
|
||||
filterFn repodb.FilterRepoFunc,
|
||||
log log.Logger, //nolint:unparam // may be used by devs for debugging
|
||||
requestedPage *gql_generated.PageInput,
|
||||
repoDB repodb.RepoDB,
|
||||
) (*gql_generated.PaginatedReposResult, error) {
|
||||
repos := []*gql_generated.RepoSummary{}
|
||||
paginatedRepos := &gql_generated.PaginatedReposResult{}
|
||||
|
||||
if requestedPage == nil {
|
||||
requestedPage = &gql_generated.PageInput{}
|
||||
}
|
||||
|
||||
skip := convert.SkipQGLField{
|
||||
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Results.NewestImage.Vulnerabilities"),
|
||||
}
|
||||
|
||||
pageInput := repodb.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
SortBy: repodb.SortCriteria(
|
||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||
),
|
||||
}
|
||||
|
||||
reposMeta, manifestMetaMap, indexDataMap, pageInfo, err := repoDB.FilterRepos(ctx, filterFn, pageInput)
|
||||
if err != nil {
|
||||
return paginatedRepos, err
|
||||
}
|
||||
|
||||
for _, repoMeta := range reposMeta {
|
||||
repoSummary := convert.RepoMeta2RepoSummary(ctx, repoMeta, manifestMetaMap, indexDataMap,
|
||||
skip, cveInfo)
|
||||
repos = append(repos, repoSummary)
|
||||
}
|
||||
|
||||
paginatedRepos.Page = &gql_generated.PageInfo{
|
||||
TotalCount: pageInfo.TotalCount,
|
||||
ItemCount: pageInfo.ItemCount,
|
||||
}
|
||||
paginatedRepos.Results = repos
|
||||
|
||||
return paginatedRepos, nil
|
||||
}
|
||||
|
||||
func globalSearch(ctx context.Context, query string, repoDB repodb.RepoDB, filter *gql_generated.Filter,
|
||||
requestedPage *gql_generated.PageInput, cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam
|
||||
) (*gql_generated.PaginatedReposResult, []*gql_generated.ImageSummary, []*gql_generated.LayerSummary, error,
|
||||
@@ -675,7 +760,7 @@ func derivedImageList(ctx context.Context, image string, digest *string, repoDB
|
||||
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
|
||||
}
|
||||
|
||||
imageRepo, imageTag := common.GetImageDirAndTag(image)
|
||||
imageRepo, imageTag := zcommon.GetImageDirAndTag(image)
|
||||
if imageTag == "" {
|
||||
return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
|
||||
}
|
||||
@@ -788,7 +873,7 @@ func baseImageList(ctx context.Context, image string, digest *string, repoDB rep
|
||||
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
|
||||
}
|
||||
|
||||
imageRepo, imageTag := common.GetImageDirAndTag(image)
|
||||
imageRepo, imageTag := zcommon.GetImageDirAndTag(image)
|
||||
|
||||
if imageTag == "" {
|
||||
return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"zotregistry.io/zot/pkg/meta/bolt"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
boltdb_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
@@ -418,7 +419,7 @@ func TestRepoListWithNewestImage(t *testing.T) {
|
||||
So(repos.Results, ShouldBeEmpty)
|
||||
})
|
||||
|
||||
Convey("RepoDB SearchRepo Bad manifest referenced", func() {
|
||||
Convey("RepoDB SearchRepo bad manifest referenced", func() {
|
||||
mockRepoDB := mocks.RepoDBMock{
|
||||
SearchReposFn: func(ctx context.Context, searchText string, filter repodb.Filter, requestedPage repodb.PageInput,
|
||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error) {
|
||||
@@ -546,8 +547,8 @@ func TestRepoListWithNewestImage(t *testing.T) {
|
||||
|
||||
for _, repoMeta := range repos {
|
||||
pageFinder.Add(repodb.DetailedRepoMeta{
|
||||
RepoMeta: repoMeta,
|
||||
UpdateTime: createTime,
|
||||
RepoMetadata: repoMeta,
|
||||
UpdateTime: createTime,
|
||||
})
|
||||
createTime = createTime.Add(time.Second)
|
||||
}
|
||||
@@ -622,6 +623,68 @@ func TestRepoListWithNewestImage(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetBookmarkedRepos(t *testing.T) {
|
||||
Convey("getBookmarkedRepos", t, func() {
|
||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover)
|
||||
_, err := getBookmarkedRepos(
|
||||
responseContext,
|
||||
mocks.CveInfoMock{},
|
||||
log.NewLogger("debug", ""),
|
||||
nil,
|
||||
mocks.RepoDBMock{
|
||||
GetBookmarkedReposFn: func(ctx context.Context) ([]string, error) {
|
||||
return []string{}, ErrTestError
|
||||
},
|
||||
},
|
||||
)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetStarredRepos(t *testing.T) {
|
||||
Convey("getStarredRepos", t, func() {
|
||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover)
|
||||
_, err := getStarredRepos(
|
||||
responseContext,
|
||||
mocks.CveInfoMock{},
|
||||
log.NewLogger("debug", ""),
|
||||
nil,
|
||||
mocks.RepoDBMock{
|
||||
GetStarredReposFn: func(ctx context.Context) ([]string, error) {
|
||||
return []string{}, ErrTestError
|
||||
},
|
||||
},
|
||||
)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetFilteredPaginatedRepos(t *testing.T) {
|
||||
Convey("getFilteredPaginatedRepos FilterRepos fails", t, func() {
|
||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover)
|
||||
_, err := getFilteredPaginatedRepos(
|
||||
responseContext,
|
||||
mocks.CveInfoMock{},
|
||||
func(repoMeta repodb.RepoMetadata) bool { return true },
|
||||
log.NewLogger("debug", ""),
|
||||
nil,
|
||||
mocks.RepoDBMock{
|
||||
FilterReposFn: func(ctx context.Context, filter repodb.FilterRepoFunc, requestedPage repodb.PageInput,
|
||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo,
|
||||
error,
|
||||
) {
|
||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
|
||||
repodb.PageInfo{}, ErrTestError
|
||||
},
|
||||
},
|
||||
)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestImageListForDigest(t *testing.T) {
|
||||
Convey("getImageList", t, func() {
|
||||
Convey("no page requested, FilterTagsFn returns error", func() {
|
||||
@@ -1028,7 +1091,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||
repos[i].Tags = matchedTags
|
||||
|
||||
pageFinder.Add(repodb.DetailedRepoMeta{
|
||||
RepoMeta: repo,
|
||||
RepoMetadata: repo,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2486,6 +2549,28 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
||||
So(len(images.Results), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Errors for cve resolvers", t, func() {
|
||||
_, err := getImageListForCVE(
|
||||
context.Background(),
|
||||
"id",
|
||||
mocks.CveInfoMock{
|
||||
GetImageListForCVEFn: func(repo, cveID string) ([]cvemodel.TagInfo, error) {
|
||||
return []cvemodel.TagInfo{}, ErrTestError
|
||||
},
|
||||
},
|
||||
nil,
|
||||
mocks.RepoDBMock{
|
||||
GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta repodb.RepoMetadata) bool,
|
||||
requestedPage repodb.PageInput,
|
||||
) ([]repodb.RepoMetadata, error) {
|
||||
return []repodb.RepoMetadata{{}}, nil
|
||||
},
|
||||
},
|
||||
log,
|
||||
)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func getPageInput(limit int, offset int) *gql_generated.PageInput {
|
||||
@@ -2726,7 +2811,7 @@ func TestDerivedImageList(t *testing.T) {
|
||||
repos[i].Tags = matchedTags
|
||||
|
||||
pageFinder.Add(repodb.DetailedRepoMeta{
|
||||
RepoMeta: repo,
|
||||
RepoMetadata: repo,
|
||||
})
|
||||
}
|
||||
repos, pageInfo := pageFinder.Page()
|
||||
@@ -2989,7 +3074,7 @@ func TestBaseImageList(t *testing.T) {
|
||||
repos[i].Tags = matchedTags
|
||||
|
||||
pageFinder.Add(repodb.DetailedRepoMeta{
|
||||
RepoMeta: repo,
|
||||
RepoMetadata: repo,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3163,7 +3248,7 @@ func TestBaseImageList(t *testing.T) {
|
||||
repos[i].Tags = matchedTags
|
||||
|
||||
pageFinder.Add(repodb.DetailedRepoMeta{
|
||||
RepoMeta: repo,
|
||||
RepoMetadata: repo,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3182,6 +3267,8 @@ func TestBaseImageList(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExpandedRepoInfo(t *testing.T) {
|
||||
log := log.NewLogger("debug", "")
|
||||
|
||||
Convey("ExpandedRepoInfo Errors", t, func() {
|
||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover)
|
||||
@@ -3198,10 +3285,22 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||
Digest: "digestIndex",
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
},
|
||||
"tagGetIndexError": {
|
||||
Digest: "errorIndexDigest",
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
},
|
||||
"tagGoodIndexBadManifests": {
|
||||
Digest: "goodIndexBadManifests",
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
},
|
||||
"tagGoodIndex1GoodManfest": {
|
||||
Digest: "goodIndexGoodManfest",
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
},
|
||||
"tagGoodIndex2GoodManfest": {
|
||||
Digest: "goodIndexGoodManfest",
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
@@ -3227,6 +3326,16 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
goodIndexGoodManfestBlob, err := json.Marshal(ispec.Index{
|
||||
Manifests: []ispec.Descriptor{
|
||||
{
|
||||
Digest: "goodManifest",
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
switch indexDigest {
|
||||
case "errorIndexDigest":
|
||||
return repodb.IndexData{}, ErrTestError
|
||||
@@ -3234,14 +3343,61 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||
return repodb.IndexData{
|
||||
IndexBlob: goodIndexBadManifestsBlob,
|
||||
}, nil
|
||||
case "goodIndexGoodManfest":
|
||||
return repodb.IndexData{
|
||||
IndexBlob: goodIndexGoodManfestBlob,
|
||||
}, nil
|
||||
default:
|
||||
return repodb.IndexData{}, nil
|
||||
}
|
||||
},
|
||||
}
|
||||
log := log.NewLogger("debug", "")
|
||||
|
||||
_, err := expandedRepoInfo(responseContext, "repo", repoDB, mocks.CveInfoMock{}, log)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Access error", t, func() {
|
||||
authzCtxKey := localCtx.GetContextKey()
|
||||
acCtxUser := localCtx.AccessControlContext{
|
||||
ReadGlobPatterns: map[string]bool{
|
||||
"repo": false,
|
||||
},
|
||||
Username: "user",
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), authzCtxKey, acCtxUser)
|
||||
|
||||
responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover)
|
||||
|
||||
_, err := expandedRepoInfo(responseContext, "repo", mocks.RepoDBMock{}, mocks.CveInfoMock{}, log)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilterFunctions(t *testing.T) {
|
||||
Convey("Filter Functions", t, func() {
|
||||
Convey("FilterByDigest bad manifest blob", func() {
|
||||
filterFunc := FilterByDigest("digest")
|
||||
ok := filterFunc(
|
||||
repodb.RepoMetadata{},
|
||||
repodb.ManifestMetadata{
|
||||
ManifestBlob: []byte("bad blob"),
|
||||
},
|
||||
)
|
||||
So(ok, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("filterDerivedImages bad manifest blob", func() {
|
||||
filterFunc := filterDerivedImages(&gql_generated.ImageSummary{})
|
||||
ok := filterFunc(
|
||||
repodb.RepoMetadata{},
|
||||
repodb.ManifestMetadata{
|
||||
ManifestBlob: []byte("bad blob"),
|
||||
},
|
||||
)
|
||||
So(ok, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -680,4 +680,20 @@ type Query {
|
||||
"Types of artifacts to return in the referrer list"
|
||||
type: [String!]
|
||||
): [Referrer]!
|
||||
|
||||
"""
|
||||
Receive RepoSummaries of repos starred by current user
|
||||
"""
|
||||
StarredRepos(
|
||||
"Sets the parameters of the requested page (how many to include and offset)"
|
||||
requestedPage: PageInput
|
||||
): PaginatedReposResult!
|
||||
|
||||
"""
|
||||
Receive RepoSummaries of repos bookmarked by current user
|
||||
"""
|
||||
BookmarkedRepos(
|
||||
"Sets the parameters of the requested page (how many to include and offset)"
|
||||
requestedPage: PageInput
|
||||
): PaginatedReposResult!
|
||||
}
|
||||
|
||||
@@ -144,6 +144,16 @@ func (r *queryResolver) Referrers(ctx context.Context, repo string, digest strin
|
||||
return referrers, nil
|
||||
}
|
||||
|
||||
// StarredRepos is the resolver for the StarredRepos field.
|
||||
func (r *queryResolver) StarredRepos(ctx context.Context, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedReposResult, error) {
|
||||
return getStarredRepos(ctx, r.cveInfo, r.log, requestedPage, r.repoDB)
|
||||
}
|
||||
|
||||
// BookmarkedRepos is the resolver for the BookmarkedRepos field.
|
||||
func (r *queryResolver) BookmarkedRepos(ctx context.Context, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedReposResult, error) {
|
||||
return getBookmarkedRepos(ctx, r.cveInfo, r.log, requestedPage, r.repoDB)
|
||||
}
|
||||
|
||||
// Query returns gql_generated.QueryResolver implementation.
|
||||
func (r *Resolver) Query() gql_generated.QueryResolver { return &queryResolver{r} }
|
||||
|
||||
|
||||
@@ -147,6 +147,26 @@ type ImageSummaryResult struct {
|
||||
Errors []ErrorGQL `json:"errors"`
|
||||
}
|
||||
|
||||
//nolint:tagliatelle // graphQL schema
|
||||
type StarredRepos struct {
|
||||
PaginatedReposResult `json:"StarredRepos"`
|
||||
}
|
||||
|
||||
//nolint:tagliatelle // graphQL schema
|
||||
type BookmarkedRepos struct {
|
||||
PaginatedReposResult `json:"BookmarkedRepos"`
|
||||
}
|
||||
|
||||
type StarredReposResponse struct {
|
||||
StarredRepos `json:"data"`
|
||||
Errors []ErrorGQL `json:"errors"`
|
||||
}
|
||||
|
||||
type BookmarkedReposResponse struct {
|
||||
BookmarkedRepos `json:"data"`
|
||||
Errors []ErrorGQL `json:"errors"`
|
||||
}
|
||||
|
||||
func readFileAndSearchString(filePath string, stringToMatch string, timeout time.Duration) (bool, error) {
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancelFunc()
|
||||
|
||||
@@ -0,0 +1,632 @@
|
||||
//go:build search && userprefs
|
||||
|
||||
package search_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"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/log"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/storage/local"
|
||||
. "zotregistry.io/zot/pkg/test"
|
||||
)
|
||||
|
||||
//nolint:dupl
|
||||
func TestUserData(t *testing.T) {
|
||||
Convey("Test user stars and bookmarks", t, func(c C) {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
defaultVal := true
|
||||
|
||||
accessibleRepo := "accessible-repo"
|
||||
forbiddenRepo := "forbidden-repo"
|
||||
tag := "0.0.1"
|
||||
|
||||
adminUser := "alice"
|
||||
adminPassword := "deepGoesTheRabbitBurrow"
|
||||
simpleUser := "test"
|
||||
simpleUserPassword := "test123"
|
||||
|
||||
twoCredTests := fmt.Sprintf("%s\n%s\n\n", getCredString(adminUser, adminPassword),
|
||||
getCredString(simpleUser, simpleUserPassword))
|
||||
|
||||
htpasswdPath := MakeHtpasswdFileFromString(twoCredTests)
|
||||
defer os.Remove(htpasswdPath)
|
||||
|
||||
conf := config.New()
|
||||
conf.Storage.RootDirectory = t.TempDir()
|
||||
conf.HTTP.Port = port
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
HTPasswd: config.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
}
|
||||
conf.HTTP.AccessControl = &config.AccessControlConfig{
|
||||
Repositories: config.Repositories{
|
||||
"**": config.PolicyGroup{
|
||||
Policies: []config.Policy{
|
||||
{
|
||||
Users: []string{simpleUser},
|
||||
Actions: []string{"read"},
|
||||
},
|
||||
},
|
||||
AnonymousPolicy: []string{"read"},
|
||||
DefaultPolicy: []string{},
|
||||
},
|
||||
forbiddenRepo: config.PolicyGroup{
|
||||
Policies: []config.Policy{
|
||||
{
|
||||
Users: []string{},
|
||||
Actions: []string{},
|
||||
},
|
||||
},
|
||||
DefaultPolicy: []string{},
|
||||
},
|
||||
},
|
||||
AdminPolicy: config.Policy{
|
||||
Users: []string{adminUser},
|
||||
Actions: []string{"read", "create", "update"},
|
||||
},
|
||||
}
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
|
||||
}
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
config, layers, manifest, err := GetImageComponents(100)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = UploadImageWithBasicAuth(
|
||||
Image{
|
||||
Config: config,
|
||||
Layers: layers,
|
||||
Manifest: manifest,
|
||||
Reference: tag,
|
||||
}, baseURL, accessibleRepo,
|
||||
adminUser, adminPassword,
|
||||
)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = UploadImageWithBasicAuth(
|
||||
Image{
|
||||
Config: config,
|
||||
Layers: layers,
|
||||
Manifest: manifest,
|
||||
Reference: tag,
|
||||
}, baseURL, forbiddenRepo,
|
||||
adminUser, adminPassword,
|
||||
)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
userStaredReposQuery := `{
|
||||
StarredRepos {
|
||||
Results {
|
||||
Name StarCount IsStarred
|
||||
NewestImage { Tag }
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
userBookmarkedReposQuery := `{
|
||||
BookmarkedRepos {
|
||||
Results {
|
||||
Name IsBookmarked
|
||||
NewestImage { Tag }
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
userprefsBaseURL := baseURL + constants.FullUserPreferencesPrefix
|
||||
|
||||
Convey("Flip starred repo authorized", func(c C) {
|
||||
clientHTTP := resty.R().SetBasicAuth(simpleUser, simpleUserPassword)
|
||||
|
||||
resp, err := clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userStaredReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct := StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoStarURL(accessibleRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userStaredReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 1)
|
||||
So(responseStruct.Results[0].Name, ShouldEqual, accessibleRepo)
|
||||
// need to update RepoSummary according to user settings
|
||||
So(responseStruct.Results[0].IsStarred, ShouldEqual, true)
|
||||
So(responseStruct.Results[0].StarCount, ShouldEqual, 1)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoStarURL(accessibleRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userStaredReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Flip starred repo unauthenticated user", func(c C) {
|
||||
clientHTTP := resty.R()
|
||||
|
||||
resp, err := clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userStaredReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct := StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoStarURL(accessibleRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userStaredReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Flip starred repo unauthorized", func(c C) {
|
||||
clientHTTP := resty.R().SetBasicAuth(simpleUser, simpleUserPassword)
|
||||
|
||||
resp, err := clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userStaredReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct := StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoStarURL(forbiddenRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userStaredReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Flip starred repo with unauthorized repo and admin user", func(c C) {
|
||||
clientHTTP := resty.R().SetBasicAuth(adminUser, adminPassword)
|
||||
|
||||
resp, err := clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userStaredReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct := StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoStarURL(forbiddenRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userStaredReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 1)
|
||||
So(responseStruct.Results[0].Name, ShouldEqual, forbiddenRepo)
|
||||
// need to update RepoSummary according to user settings
|
||||
So(responseStruct.Results[0].IsStarred, ShouldEqual, true)
|
||||
So(responseStruct.Results[0].StarCount, ShouldEqual, 1)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoStarURL(forbiddenRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userStaredReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Flip bookmark repo authorized", func(c C) {
|
||||
clientHTTP := resty.R().SetBasicAuth(simpleUser, simpleUserPassword)
|
||||
|
||||
resp, err := clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userBookmarkedReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct := BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoBookmarkURL(accessibleRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userBookmarkedReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 1)
|
||||
So(responseStruct.Results[0].Name, ShouldEqual, accessibleRepo)
|
||||
// need to update RepoSummary according to user settings
|
||||
So(responseStruct.Results[0].IsBookmarked, ShouldEqual, true)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoBookmarkURL(accessibleRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userBookmarkedReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Flip bookmark repo unauthenticated user", func(c C) {
|
||||
clientHTTP := resty.R()
|
||||
|
||||
resp, err := clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userBookmarkedReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct := BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoBookmarkURL(accessibleRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userBookmarkedReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Flip bookmark repo unauthorized", func(c C) {
|
||||
clientHTTP := resty.R().SetBasicAuth(simpleUser, simpleUserPassword)
|
||||
|
||||
resp, err := clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userBookmarkedReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct := BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoBookmarkURL(forbiddenRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userBookmarkedReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Flip bookmarked unauthorized repo and admin user", func(c C) {
|
||||
clientHTTP := resty.R().SetBasicAuth(adminUser, adminPassword)
|
||||
|
||||
resp, err := clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userBookmarkedReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct := BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoBookmarkURL(forbiddenRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userBookmarkedReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 1)
|
||||
So(responseStruct.Results[0].Name, ShouldEqual, forbiddenRepo)
|
||||
// need to update RepoSummary according to user settings
|
||||
So(responseStruct.Results[0].IsBookmarked, ShouldEqual, true)
|
||||
|
||||
resp, err = clientHTTP.Put(userprefsBaseURL + PutRepoBookmarkURL(forbiddenRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = clientHTTP.Get(baseURL + constants.FullSearchPrefix +
|
||||
"?query=" + url.QueryEscape(userBookmarkedReposQuery))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestChangingRepoState(t *testing.T) {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
defaultVal := true
|
||||
|
||||
simpleUser := "test"
|
||||
simpleUserPassword := "test123"
|
||||
|
||||
forbiddenRepo := "forbidden"
|
||||
accesibleRepo := "accesible"
|
||||
|
||||
credTests := fmt.Sprintf("%s\n\n", getCredString(simpleUser, simpleUserPassword))
|
||||
|
||||
htpasswdPath := MakeHtpasswdFileFromString(credTests)
|
||||
defer os.Remove(htpasswdPath)
|
||||
|
||||
conf := config.New()
|
||||
conf.Storage.RootDirectory = t.TempDir()
|
||||
conf.HTTP.Port = port
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
HTPasswd: config.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
}
|
||||
conf.HTTP.AccessControl = &config.AccessControlConfig{
|
||||
Repositories: config.Repositories{
|
||||
"**": config.PolicyGroup{
|
||||
Policies: []config.Policy{
|
||||
{
|
||||
Users: []string{simpleUser},
|
||||
Actions: []string{"read"},
|
||||
},
|
||||
},
|
||||
AnonymousPolicy: []string{"read"},
|
||||
DefaultPolicy: []string{},
|
||||
},
|
||||
forbiddenRepo: config.PolicyGroup{
|
||||
Policies: []config.Policy{
|
||||
{
|
||||
Users: []string{},
|
||||
Actions: []string{},
|
||||
},
|
||||
},
|
||||
DefaultPolicy: []string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
|
||||
}
|
||||
|
||||
gqlStarredRepos := `
|
||||
{
|
||||
StarredRepos() {
|
||||
Results {
|
||||
Name
|
||||
StarCount
|
||||
IsBookmarked
|
||||
IsStarred
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
gqlBookmarkedRepos := `
|
||||
{
|
||||
BookmarkedRepos() {
|
||||
Results {
|
||||
Name
|
||||
StarCount
|
||||
IsBookmarked
|
||||
IsStarred
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
img, err := GetRandomImage("tag")
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// ------ Create the test repos
|
||||
defaultStore := local.NewImageStore(conf.Storage.RootDirectory, false, 0, false, false,
|
||||
log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), nil, nil)
|
||||
|
||||
err = WriteImageToFileSystem(img, accesibleRepo, storage.StoreController{
|
||||
DefaultStore: defaultStore,
|
||||
})
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
err = WriteImageToFileSystem(img, forbiddenRepo, storage.StoreController{
|
||||
DefaultStore: defaultStore,
|
||||
})
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
|
||||
ctlrManager.StartAndWait(port)
|
||||
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
simpleUserClient := resty.R().SetBasicAuth(simpleUser, simpleUserPassword)
|
||||
anonynousClient := resty.R()
|
||||
|
||||
userprefsBaseURL := baseURL + constants.FullUserPreferencesPrefix
|
||||
|
||||
Convey("PutStars", t, func() {
|
||||
resp, err := simpleUserClient.Put(userprefsBaseURL + PutRepoStarURL(accesibleRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = simpleUserClient.Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(gqlStarredRepos))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
responseStruct := StarredReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 1)
|
||||
So(responseStruct.Results[0].IsStarred, ShouldBeTrue)
|
||||
So(responseStruct.Results[0].Name, ShouldResemble, accesibleRepo)
|
||||
|
||||
resp, err = anonynousClient.Put(userprefsBaseURL + PutRepoStarURL(accesibleRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
|
||||
})
|
||||
//
|
||||
Convey("PutBookmark", t, func() {
|
||||
resp, err := simpleUserClient.Put(userprefsBaseURL + PutRepoBookmarkURL(accesibleRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = simpleUserClient.Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(gqlBookmarkedRepos))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
responseStruct := BookmarkedReposResponse{}
|
||||
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(responseStruct.Results), ShouldEqual, 1)
|
||||
So(responseStruct.Results[0].IsBookmarked, ShouldBeTrue)
|
||||
So(responseStruct.Results[0].Name, ShouldResemble, accesibleRepo)
|
||||
|
||||
resp, err = anonynousClient.Put(userprefsBaseURL + PutRepoBookmarkURL(accesibleRepo))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
|
||||
func PutRepoStarURL(repo string) string {
|
||||
return fmt.Sprintf("?repo=%s&action=toggleStar", repo)
|
||||
}
|
||||
|
||||
func PutRepoBookmarkURL(repo string) string {
|
||||
return fmt.Sprintf("?repo=%s&action=toggleBookmark", repo)
|
||||
}
|
||||
|
||||
func getCredString(username, password string) string {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
usernameAndHash := fmt.Sprintf("%s:%s", username, string(hash))
|
||||
|
||||
return usernameAndHash
|
||||
}
|
||||
Reference in New Issue
Block a user