mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-04 14:48:42 +00:00
Update database in one transaction on syncing user repositorys (#513)
* userSyncer Sync commit changes in one transaction * cover new cases with unit tests
This commit is contained in:
parent
82fd65665f
commit
07b32fa0fe
8 changed files with 75 additions and 44 deletions
|
@ -19,7 +19,6 @@ package model
|
||||||
type PermStore interface {
|
type PermStore interface {
|
||||||
PermFind(user *User, repo *Repo) (*Perm, error)
|
PermFind(user *User, repo *Repo) (*Perm, error)
|
||||||
PermUpsert(perm *Perm) error
|
PermUpsert(perm *Perm) error
|
||||||
PermBatch(perms []*Perm) error
|
|
||||||
PermDelete(perm *Perm) error
|
PermDelete(perm *Perm) error
|
||||||
PermFlush(user *User, before int64) error
|
PermFlush(user *User, before int64) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
"github.com/woodpecker-ci/woodpecker/server/model"
|
||||||
|
@ -70,24 +71,24 @@ func (s *Syncer) Sync(ctx context.Context, user *model.User) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var remoteRepos []*model.Repo
|
remoteRepos := make([]*model.Repo, 0, len(repos))
|
||||||
var perms []*model.Perm
|
|
||||||
|
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
if s.Match(repo) {
|
if s.Match(repo) {
|
||||||
remoteRepos = append(remoteRepos, repo)
|
repo.Perm = &model.Perm{
|
||||||
perm := model.Perm{
|
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
|
RepoID: repo.ID,
|
||||||
Repo: repo.FullName,
|
Repo: repo.FullName,
|
||||||
Pull: true,
|
|
||||||
Synced: unix,
|
Synced: unix,
|
||||||
}
|
}
|
||||||
remotePerm, err := s.Remote.Perm(ctx, user, repo.Owner, repo.Name)
|
remotePerm, err := s.Remote.Perm(ctx, user, repo.Owner, repo.Name)
|
||||||
if err == nil && remotePerm != nil {
|
if err != nil {
|
||||||
perm.Push = remotePerm.Push
|
return fmt.Errorf("could not fetch permission of repo '%s': %v", repo.FullName, err)
|
||||||
perm.Admin = remotePerm.Admin
|
|
||||||
}
|
}
|
||||||
perms = append(perms, &perm)
|
repo.Perm.Pull = remotePerm.Pull
|
||||||
|
repo.Perm.Push = remotePerm.Push
|
||||||
|
repo.Perm.Admin = remotePerm.Admin
|
||||||
|
|
||||||
|
remoteRepos = append(remoteRepos, repo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,11 +97,6 @@ func (s *Syncer) Sync(ctx context.Context, user *model.User) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.Store.PermBatch(perms)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is here as a precaution. I want to make sure that if an api
|
// this is here as a precaution. I want to make sure that if an api
|
||||||
// call to the version control system fails and (for some reason) returns
|
// call to the version control system fails and (for some reason) returns
|
||||||
// an empty list, we don't wipe out the user repository permissions.
|
// an empty list, we don't wipe out the user repository permissions.
|
||||||
|
|
|
@ -55,10 +55,12 @@ func TestRepoListLatest(t *testing.T) {
|
||||||
assert.NoError(t, store.CreateRepo(repo2))
|
assert.NoError(t, store.CreateRepo(repo2))
|
||||||
assert.NoError(t, store.CreateRepo(repo3))
|
assert.NoError(t, store.CreateRepo(repo3))
|
||||||
|
|
||||||
assert.NoError(t, store.PermBatch([]*model.Perm{
|
for _, perm := range []*model.Perm{
|
||||||
{UserID: user.ID, Repo: repo1.FullName, Push: true, Admin: false},
|
{UserID: user.ID, Repo: repo1.FullName, Push: true, Admin: false},
|
||||||
{UserID: user.ID, Repo: repo2.FullName, Push: true, Admin: true},
|
{UserID: user.ID, Repo: repo2.FullName, Push: true, Admin: true},
|
||||||
}))
|
} {
|
||||||
|
assert.NoError(t, store.PermUpsert(perm))
|
||||||
|
}
|
||||||
|
|
||||||
build1 := &model.Build{
|
build1 := &model.Build{
|
||||||
RepoID: repo1.ID,
|
RepoID: repo1.ID,
|
||||||
|
|
|
@ -17,6 +17,8 @@ package datastore
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
"github.com/woodpecker-ci/woodpecker/server/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,19 +31,26 @@ func (s storage) PermFind(user *model.User, repo *model.Repo) (*model.Perm, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s storage) PermUpsert(perm *model.Perm) error {
|
func (s storage) PermUpsert(perm *model.Perm) error {
|
||||||
if len(perm.Repo) == 0 && perm.RepoID == 0 {
|
|
||||||
return fmt.Errorf("could not determine repo for permission: %v", perm)
|
|
||||||
}
|
|
||||||
|
|
||||||
sess := s.engine.NewSession()
|
sess := s.engine.NewSession()
|
||||||
defer sess.Close()
|
defer sess.Close()
|
||||||
if err := sess.Begin(); err != nil {
|
if err := sess.Begin(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup repo based on name if possible (preserve old behaviour)
|
if err := s.permUpsert(sess, perm); err != nil {
|
||||||
// TODO: check if needed
|
return err
|
||||||
if len(perm.Repo) != 0 {
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s storage) permUpsert(sess *xorm.Session, perm *model.Perm) error {
|
||||||
|
if perm.RepoID == 0 && len(perm.Repo) == 0 {
|
||||||
|
return fmt.Errorf("could not determine repo for permission: %v", perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup repo based on name if possible
|
||||||
|
if perm.RepoID == 0 && len(perm.Repo) != 0 {
|
||||||
if r, err := s.getRepoName(sess, perm.Repo); err != nil {
|
if r, err := s.getRepoName(sess, perm.Repo); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
|
@ -62,20 +71,7 @@ func (s storage) PermUpsert(perm *model.Perm) error {
|
||||||
// only Insert set auto created ID back to object
|
// only Insert set auto created ID back to object
|
||||||
_, err = sess.Insert(perm)
|
_, err = sess.Insert(perm)
|
||||||
}
|
}
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return sess.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s storage) PermBatch(perms []*model.Perm) error {
|
|
||||||
for i := range perms {
|
|
||||||
if err := s.PermUpsert(perms[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s storage) PermDelete(perm *model.Perm) error {
|
func (s storage) PermDelete(perm *model.Perm) error {
|
||||||
|
|
|
@ -74,6 +74,8 @@ func (s storage) RepoList(user *model.User, owned bool) ([]*model.Repo, error) {
|
||||||
Find(&repos)
|
Find(&repos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RepoBatch Sync batch of repos from SCM (with permissions) to store (create if not exist else update)
|
||||||
|
// TODO: only store activated repos ...
|
||||||
func (s storage) RepoBatch(repos []*model.Repo) error {
|
func (s storage) RepoBatch(repos []*model.Repo) error {
|
||||||
sess := s.engine.NewSession()
|
sess := s.engine.NewSession()
|
||||||
defer sess.Close()
|
defer sess.Close()
|
||||||
|
@ -86,12 +88,14 @@ func (s storage) RepoBatch(repos []*model.Repo) error {
|
||||||
log.Debug().Msgf("skip insert/update repo: %#v", repos[i])
|
log.Debug().Msgf("skip insert/update repo: %#v", repos[i])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
exist, err := sess.
|
exist, err := sess.
|
||||||
Where("repo_owner = ? AND repo_name = ?", repos[i].Owner, repos[i].Name).
|
Where("repo_owner = ? AND repo_name = ?", repos[i].Owner, repos[i].Name).
|
||||||
Exist(new(model.Repo))
|
Exist(new(model.Repo))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist {
|
if exist {
|
||||||
if _, err := sess.
|
if _, err := sess.
|
||||||
Where("repo_owner = ? AND repo_name = ?", repos[i].Owner, repos[i].Name).
|
Where("repo_owner = ? AND repo_name = ?", repos[i].Owner, repos[i].Name).
|
||||||
|
@ -112,6 +116,14 @@ func (s storage) RepoBatch(repos []*model.Repo) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if repos[i].Perm != nil {
|
||||||
|
repos[i].Perm.RepoID = repos[i].ID
|
||||||
|
repos[i].Perm.Repo = repos[i].FullName
|
||||||
|
if err := s.permUpsert(sess, repos[i].Perm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
|
|
|
@ -16,6 +16,7 @@ package datastore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/franela/goblin"
|
"github.com/franela/goblin"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -290,6 +291,13 @@ func TestRepoBatch(t *testing.T) {
|
||||||
Owner: "foo",
|
Owner: "foo",
|
||||||
Name: "bar",
|
Name: "bar",
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
|
Perm: &model.Perm{
|
||||||
|
UserID: 1,
|
||||||
|
Pull: true,
|
||||||
|
Push: true,
|
||||||
|
Admin: true,
|
||||||
|
Synced: time.Now().Unix(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
UserID: 1,
|
UserID: 1,
|
||||||
|
@ -317,16 +325,33 @@ func TestRepoBatch(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check DB state
|
||||||
|
perm, err := store.PermFind(&model.User{ID: 1}, repos[0])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, perm.Admin)
|
||||||
|
|
||||||
repo := &model.Repo{
|
repo := &model.Repo{
|
||||||
FullName: "foo/bar",
|
FullName: "foo/bar",
|
||||||
Owner: "foo",
|
Owner: "foo",
|
||||||
Name: "bar",
|
Name: "bar",
|
||||||
|
Perm: &model.Perm{
|
||||||
|
UserID: 1,
|
||||||
|
Pull: true,
|
||||||
|
Push: true,
|
||||||
|
Admin: false,
|
||||||
|
Synced: time.Now().Unix(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
assert.NoError(t, store.RepoBatch([]*model.Repo{repo}))
|
assert.NoError(t, store.RepoBatch([]*model.Repo{repo}))
|
||||||
assert.EqualValues(t, repos[0].ID, repo.ID)
|
assert.EqualValues(t, repos[0].ID, repo.ID)
|
||||||
_, err := store.engine.ID(repo.ID).Get(repo)
|
|
||||||
|
// check current DB state
|
||||||
|
_, err = store.engine.ID(repo.ID).Get(repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, repo.IsActive)
|
assert.True(t, repo.IsActive)
|
||||||
|
perm, err = store.PermFind(&model.User{ID: 1}, repos[0])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, perm.Admin)
|
||||||
|
|
||||||
allRepos := make([]*model.Repo, 0, 4)
|
allRepos := make([]*model.Repo, 0, 4)
|
||||||
assert.NoError(t, store.engine.Find(&allRepos))
|
assert.NoError(t, store.engine.Find(&allRepos))
|
||||||
|
|
|
@ -213,10 +213,12 @@ func TestUsers(t *testing.T) {
|
||||||
g.Assert(store.CreateRepo(repo2)).IsNil()
|
g.Assert(store.CreateRepo(repo2)).IsNil()
|
||||||
g.Assert(store.CreateRepo(repo3)).IsNil()
|
g.Assert(store.CreateRepo(repo3)).IsNil()
|
||||||
|
|
||||||
g.Assert(store.PermBatch([]*model.Perm{
|
for _, perm := range []*model.Perm{
|
||||||
{UserID: user.ID, Repo: repo1.FullName, Push: true, Admin: false},
|
{UserID: user.ID, Repo: repo1.FullName, Push: true, Admin: false},
|
||||||
{UserID: user.ID, Repo: repo2.FullName, Push: false, Admin: true},
|
{UserID: user.ID, Repo: repo2.FullName, Push: false, Admin: true},
|
||||||
})).IsNil()
|
} {
|
||||||
|
g.Assert(store.PermUpsert(perm)).IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
build1 := &model.Build{
|
build1 := &model.Build{
|
||||||
RepoID: repo1.ID,
|
RepoID: repo1.ID,
|
||||||
|
|
|
@ -106,12 +106,11 @@ type Store interface {
|
||||||
// RepoList TODO: paginate
|
// RepoList TODO: paginate
|
||||||
RepoList(user *model.User, owned bool) ([]*model.Repo, error)
|
RepoList(user *model.User, owned bool) ([]*model.Repo, error)
|
||||||
RepoListLatest(*model.User) ([]*model.Feed, error)
|
RepoListLatest(*model.User) ([]*model.Feed, error)
|
||||||
// RepoBatch TODO: only store activated repos ...
|
// RepoBatch Sync batch of repos from SCM (with permissions) to store (create if not exist else update)
|
||||||
RepoBatch([]*model.Repo) error
|
RepoBatch([]*model.Repo) error
|
||||||
|
|
||||||
PermFind(user *model.User, repo *model.Repo) (*model.Perm, error)
|
PermFind(user *model.User, repo *model.Repo) (*model.Perm, error)
|
||||||
PermUpsert(perm *model.Perm) error
|
PermUpsert(perm *model.Perm) error
|
||||||
PermBatch(perms []*model.Perm) error
|
|
||||||
PermDelete(perm *model.Perm) error
|
PermDelete(perm *model.Perm) error
|
||||||
PermFlush(user *model.User, before int64) error
|
PermFlush(user *model.User, before int64) error
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue