Retrieve all user repo perms with a single API call (#3211)

This pull request addresses the issue https://github.com/woodpecker-ci/woodpecker/issues/3210.

Ideally, the Bitbucket API should include repository permissions when
utilizing the 'get all repositories' endpoint. However, as it currently
does not provide this information, a viable solution is to fetch all
permissions for every repository and then employ a dictionary to
associate each repository with its respective permissions.

Without implementing this fix, logging in becomes problematic for users
with access to a substantial number of repositories (300+), as the
process takes over 2 minutes to complete.

---------

Co-authored-by: Alberto Alcón <albertoalcon@bit2me.com>
This commit is contained in:
Alconety 2024-01-19 04:15:47 +01:00 committed by GitHub
parent 7b29d1da49
commit 07479dd645
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 63 additions and 26 deletions

View file

@ -203,6 +203,16 @@ func (c *config) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error
return nil, err return nil, err
} }
userPermisions, err := client.ListPermissionsAll()
if err != nil {
return nil, err
}
userPermissionsByRepo := make(map[string]*internal.RepoPerm)
for _, permission := range userPermisions {
userPermissionsByRepo[permission.Repo.FullName] = permission
}
var all []*model.Repo var all []*model.Repo
for _, workspace := range workspaces { for _, workspace := range workspaces {
repos, err := client.ListReposAll(workspace.Slug) repos, err := client.ListReposAll(workspace.Slug)
@ -210,12 +220,9 @@ func (c *config) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error
return nil, err return nil, err
} }
for _, repo := range repos { for _, repo := range repos {
perm, err := client.GetPermission(repo.FullName) if perm, ok := userPermissionsByRepo[repo.FullName]; ok {
if err != nil { all = append(all, convertRepo(repo, perm))
return nil, err
} }
all = append(all, convertRepo(repo, perm))
} }
} }
return all, nil return all, nil

View file

@ -185,21 +185,11 @@ func getUserRepos(c *gin.Context) {
} }
} }
func permission(p string) string {
return fmt.Sprintf(permissionPayload, p)
}
func getPermissions(c *gin.Context) { func getPermissions(c *gin.Context) {
query := c.Request.URL.Query()["q"][0] if c.Query("page") == "" || c.Query("page") == "1" {
switch query { c.String(200, permissionsPayLoad)
case `repository.full_name="test_name/permission_read"`: } else {
c.String(200, permission("read")) c.String(200, "{\"values\":[]}")
case `repository.full_name="test_name/permission_write"`:
c.String(200, permission("write"))
case `repository.full_name="test_name/permission_admin"`:
c.String(200, permission("admin"))
default:
c.String(200, permission("read"))
} }
} }
@ -367,14 +357,35 @@ const workspacesPayload = `
} }
` `
const permissionPayload = ` const permissionsPayLoad = `
{ {
"pagelen": 1, "pagelen": 100,
"page": 1,
"values": [ "values": [
{ {
"permission": "%s" "repository": {
"full_name": "test_name/repo_name"
},
"permission": "read"
},
{
"repository": {
"full_name": "test_name/permission_read"
},
"permission": "read"
},
{
"repository": {
"full_name": "test_name/permission_write"
},
"permission": "write"
},
{
"repository": {
"full_name": "test_name/permission_admin"
},
"permission": "admin"
} }
], ]
"page": 1
} }
` `

View file

@ -38,7 +38,8 @@ const (
const ( const (
pathUser = "%s/2.0/user/" pathUser = "%s/2.0/user/"
pathEmails = "%s/2.0/user/emails" pathEmails = "%s/2.0/user/emails"
pathPermissions = "%s/2.0/user/permissions/repositories?q=repository.full_name=%q" pathPermission = "%s/2.0/user/permissions/repositories?q=repository.full_name=%q"
pathPermissions = "%s/2.0/user/permissions/repositories?%s"
pathWorkspaces = "%s/2.0/workspaces/?%s" pathWorkspaces = "%s/2.0/workspaces/?%s"
pathWorkspace = "%s/2.0/workspaces/%s" pathWorkspace = "%s/2.0/workspaces/%s"
pathRepo = "%s/2.0/repositories/%s/%s" pathRepo = "%s/2.0/repositories/%s/%s"
@ -161,7 +162,7 @@ func (c *Client) CreateStatus(owner, name, revision string, status *PipelineStat
func (c *Client) GetPermission(fullName string) (*RepoPerm, error) { func (c *Client) GetPermission(fullName string) (*RepoPerm, error) {
out := new(RepoPermResp) out := new(RepoPermResp)
uri := fmt.Sprintf(pathPermissions, c.base, fullName) uri := fmt.Sprintf(pathPermission, c.base, fullName)
_, err := c.do(uri, get, nil, out) _, err := c.do(uri, get, nil, out)
if err != nil { if err != nil {
return nil, err return nil, err
@ -173,6 +174,23 @@ func (c *Client) GetPermission(fullName string) (*RepoPerm, error) {
return out.Values[0], nil return out.Values[0], nil
} }
func (c *Client) ListPermissions(opts *ListOpts) (*RepoPermResp, error) {
out := new(RepoPermResp)
uri := fmt.Sprintf(pathPermissions, c.base, opts.Encode())
_, err := c.do(uri, get, nil, out)
return out, err
}
func (c *Client) ListPermissionsAll() ([]*RepoPerm, error) {
return shared_utils.Paginate(func(page int) ([]*RepoPerm, error) {
resp, err := c.ListPermissions(&ListOpts{Page: page, PageLen: 100})
if err != nil {
return nil, err
}
return resp.Values, nil
})
}
func (c *Client) ListBranches(owner, name string, opts *ListOpts) ([]*Branch, error) { func (c *Client) ListBranches(owner, name string, opts *ListOpts) ([]*Branch, error) {
out := new(BranchResp) out := new(BranchResp)
uri := fmt.Sprintf(pathBranches, c.base, owner, name, opts.Encode()) uri := fmt.Sprintf(pathBranches, c.base, owner, name, opts.Encode())

View file

@ -254,6 +254,7 @@ type RepoPermResp struct {
type RepoPerm struct { type RepoPerm struct {
Permission string `json:"permission"` Permission string `json:"permission"`
Repo Repo `json:"repository"`
} }
type BranchResp struct { type BranchResp struct {