180 lines
3.8 KiB
Go
180 lines
3.8 KiB
Go
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||
|
// that can be found in the LICENSE file.
|
||
|
|
||
|
package syncer
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"time"
|
||
|
|
||
|
"github.com/drone/drone/core"
|
||
|
|
||
|
"github.com/sirupsen/logrus"
|
||
|
)
|
||
|
|
||
|
// New returns a new Synchronizer.
|
||
|
func New(
|
||
|
repoz core.RepositoryService,
|
||
|
repos core.RepositoryStore,
|
||
|
users core.UserStore,
|
||
|
batch core.Batcher,
|
||
|
) *Synchronizer {
|
||
|
return &Synchronizer{
|
||
|
repoz: repoz,
|
||
|
repos: repos,
|
||
|
users: users,
|
||
|
batch: batch,
|
||
|
match: noopFilter,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Synchronizer synchronizes user repositories and permissions
|
||
|
// between a remote source code management system and the local
|
||
|
// data store.
|
||
|
type Synchronizer struct {
|
||
|
repoz core.RepositoryService
|
||
|
repos core.RepositoryStore
|
||
|
users core.UserStore
|
||
|
batch core.Batcher
|
||
|
match FilterFunc
|
||
|
}
|
||
|
|
||
|
// SetFilter sets the filter function.
|
||
|
func (s *Synchronizer) SetFilter(fn FilterFunc) {
|
||
|
s.match = fn
|
||
|
}
|
||
|
|
||
|
// Sync synchonrizes the user repository list in 6 easy steps.
|
||
|
func (s *Synchronizer) Sync(ctx context.Context, user *core.User) (*core.Batch, error) {
|
||
|
logger := logrus.WithField("login", user.Login)
|
||
|
logger.Debugln("syncer: begin repository sync")
|
||
|
|
||
|
defer func() {
|
||
|
// taking the paranoid approach to recover from
|
||
|
// a panic that should absolutely never happen.
|
||
|
if err := recover(); err != nil {
|
||
|
logger = logger.WithField("error", err)
|
||
|
logger.Errorln("syncer: unexpected panic")
|
||
|
}
|
||
|
|
||
|
// when the synchronization process is complete
|
||
|
// be sure to update the user sync date.
|
||
|
user.Syncing = false
|
||
|
user.Synced = time.Now().Unix()
|
||
|
s.users.Update(context.Background(), user)
|
||
|
}()
|
||
|
|
||
|
if user.Syncing == false {
|
||
|
user.Syncing = true
|
||
|
err := s.users.Update(ctx, user)
|
||
|
if err != nil {
|
||
|
logger = logger.WithError(err)
|
||
|
logger.Warnln("syncer: cannot update user")
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
batch := &core.Batch{}
|
||
|
remote := map[string]*core.Repository{}
|
||
|
local := map[string]*core.Repository{}
|
||
|
|
||
|
//
|
||
|
// STEP1: get the list of repositories from the remote
|
||
|
// source code management system (e.g. GitHub).
|
||
|
//
|
||
|
|
||
|
{
|
||
|
repos, err := s.repoz.List(ctx, user)
|
||
|
if err != nil {
|
||
|
logger = logger.WithError(err)
|
||
|
logger.Warnln("syncer: cannot get remote repository list")
|
||
|
return nil, err
|
||
|
}
|
||
|
for _, repo := range repos {
|
||
|
if s.match(repo) {
|
||
|
remote[repo.UID] = repo
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// STEP2: get the list of repositories stored in the
|
||
|
// local database.
|
||
|
//
|
||
|
|
||
|
{
|
||
|
repos, err := s.repos.List(ctx, user.ID)
|
||
|
if err != nil {
|
||
|
logger = logger.WithError(err)
|
||
|
logger.Warnln("syncer: cannot get cached repository list")
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
for _, repo := range repos {
|
||
|
local[repo.UID] = repo
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// STEP3 find repos that exist in the remote system,
|
||
|
// but do not exist locally. Insert.
|
||
|
//
|
||
|
|
||
|
for k, v := range remote {
|
||
|
_, ok := local[k]
|
||
|
if ok {
|
||
|
continue
|
||
|
}
|
||
|
v.Synced = time.Now().Unix()
|
||
|
v.Created = time.Now().Unix()
|
||
|
v.Updated = time.Now().Unix()
|
||
|
v.Version = 1
|
||
|
batch.Insert = append(batch.Insert, v)
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// STEP4 find repos that exist in the remote system and
|
||
|
// in the local system, but with incorect data. Update.
|
||
|
//
|
||
|
|
||
|
for k, v := range local {
|
||
|
vv, ok := remote[k]
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
if diff(v, vv) {
|
||
|
merge(v, vv)
|
||
|
v.Synced = time.Now().Unix()
|
||
|
v.Updated = time.Now().Unix()
|
||
|
batch.Update = append(batch.Update, v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// STEP5 find repos that exist in the local system,
|
||
|
// but not in the remote system. Revoke permissions.
|
||
|
//
|
||
|
|
||
|
for k, v := range local {
|
||
|
_, ok := remote[k]
|
||
|
if ok {
|
||
|
continue
|
||
|
}
|
||
|
batch.Revoke = append(batch.Revoke, v)
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// STEP6 update the database.
|
||
|
//
|
||
|
|
||
|
if err := s.batch.Batch(ctx, user, batch); err != nil {
|
||
|
logger = logger.WithError(err)
|
||
|
logger.Warnln("syncer: cannot batch update")
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
logger.Debugln("syncer: finished repository sync")
|
||
|
return batch, nil
|
||
|
}
|