From 1e946801e8aa894662f938411fd4fda9ced9519a Mon Sep 17 00:00:00 2001 From: noah Date: Tue, 24 Aug 2021 23:00:15 +0900 Subject: [PATCH 1/4] feat: add GITPLOY_REPOSITORY_ENTRIES env --- cmd/server/config.go | 3 ++- cmd/server/main.go | 1 + internal/interactor/interactor.go | 5 ++++- internal/interactor/sync.go | 14 ++++++++++++++ internal/server/api/v1/sync/interface.go | 3 +++ internal/server/api/v1/sync/syncher.go | 10 +++++++++- 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/cmd/server/config.go b/cmd/server/config.go index 25efff1b..920e1525 100644 --- a/cmd/server/config.go +++ b/cmd/server/config.go @@ -24,7 +24,8 @@ type ( ServerProxyProto string `default:"https" split_words:"true"` ServerProxyPort string `default:"8081" split_words:"true"` - AdminUsers []string `split_words:"true"` + RepositoryEntries []string `split_words:"true"` + AdminUsers []string `split_words:"true"` License string `split_words:"true"` } diff --git a/cmd/server/main.go b/cmd/server/main.go index c86e8379..f7d333c9 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -118,6 +118,7 @@ func NewInteractor(c *Config) server.Interactor { &interactor.InteractorConfig{ ServerHost: c.ServerHost, ServerProto: c.ServerProto, + RepoEntries: c.RepositoryEntries, AdminUsers: c.AdminUsers, LicenseKey: c.License, Store: newStore(c), diff --git a/internal/interactor/interactor.go b/internal/interactor/interactor.go index aa284564..7019aa4c 100644 --- a/internal/interactor/interactor.go +++ b/internal/interactor/interactor.go @@ -11,6 +11,7 @@ type ( ServerHost string ServerProto string + repoEntries []string // Admin Users admins []string @@ -31,7 +32,8 @@ type ( ServerHost string ServerProto string - AdminUsers []string + RepoEntries []string + AdminUsers []string LicenseKey string @@ -44,6 +46,7 @@ func NewInteractor(c *InteractorConfig) *Interactor { i := &Interactor{ ServerHost: c.ServerHost, ServerProto: c.ServerProto, + repoEntries: c.RepoEntries, admins: c.AdminUsers, licenseKey: c.LicenseKey, Store: c.Store, diff --git a/internal/interactor/sync.go b/internal/interactor/sync.go index 73c7ca0e..0cb60201 100644 --- a/internal/interactor/sync.go +++ b/internal/interactor/sync.go @@ -8,6 +8,20 @@ import ( "github.com/hanjunlee/gitploy/vo" ) +func (i *Interactor) IsEntryRepo(ctx context.Context, namespace string) bool { + if i.repoEntries == nil { + return true + } + + for _, r := range i.repoEntries { + if namespace == r { + return true + } + } + + return false +} + func (i *Interactor) SyncRemoteRepo(ctx context.Context, u *ent.User, re *vo.RemoteRepo) error { var ( r *ent.Repo diff --git a/internal/server/api/v1/sync/interface.go b/internal/server/api/v1/sync/interface.go index e5951c66..d35412a8 100644 --- a/internal/server/api/v1/sync/interface.go +++ b/internal/server/api/v1/sync/interface.go @@ -1,3 +1,5 @@ +//go:generate mockgen -source ./interface.go -destination ./mock/interactor.go -package mock + package sync import ( @@ -11,6 +13,7 @@ import ( type ( Interactor interface { ListRemoteRepos(ctx context.Context, u *ent.User) ([]*vo.RemoteRepo, error) + IsEntryRepo(ctx context.Context, namespace string) bool SyncRemoteRepo(ctx context.Context, u *ent.User, re *vo.RemoteRepo) error DeletePermsOfUserLessThanUpdatedAt(ctx context.Context, u *ent.User, t time.Time) (int, error) } diff --git a/internal/server/api/v1/sync/syncher.go b/internal/server/api/v1/sync/syncher.go index 615ed834..adb3e8a8 100644 --- a/internal/server/api/v1/sync/syncher.go +++ b/internal/server/api/v1/sync/syncher.go @@ -44,12 +44,20 @@ func (s *Syncher) Sync(c *gin.Context) { } syncTime := time.Now() + syncCnt := 0 for _, re := range remotes { + // Skip un-selected repositories. + if !s.i.IsEntryRepo(ctx, re.Namespace) { + continue + } + if err := s.i.SyncRemoteRepo(ctx, u, re); err != nil { s.log.Error("It has failed to sync with the remote repository.", zap.Error(err), zap.String("repo_id", re.ID)) + continue } + syncCnt = syncCnt + 1 } - s.log.Debug(fmt.Sprintf("Schronize with %d repositories.", len(remotes)), zap.String("user_id", u.ID)) + s.log.Debug(fmt.Sprintf("Processed to schronize with %d repositories.", syncCnt), zap.String("user_id", u.ID)) // Delete staled perms. var cnt int From bf86ba6405ab90b5202774c5489d1d68f28e0cd9 Mon Sep 17 00:00:00 2001 From: noah Date: Tue, 24 Aug 2021 23:16:33 +0900 Subject: [PATCH 2/4] feat: add unit-test for sync --- .../server/api/v1/sync/mock/interactor.go | 96 +++++++++++++++++++ internal/server/api/v1/sync/syncher_test.go | 82 ++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 internal/server/api/v1/sync/mock/interactor.go create mode 100644 internal/server/api/v1/sync/syncher_test.go diff --git a/internal/server/api/v1/sync/mock/interactor.go b/internal/server/api/v1/sync/mock/interactor.go new file mode 100644 index 00000000..863e2e4e --- /dev/null +++ b/internal/server/api/v1/sync/mock/interactor.go @@ -0,0 +1,96 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./interface.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + time "time" + + gomock "github.com/golang/mock/gomock" + ent "github.com/hanjunlee/gitploy/ent" + vo "github.com/hanjunlee/gitploy/vo" +) + +// MockInteractor is a mock of Interactor interface. +type MockInteractor struct { + ctrl *gomock.Controller + recorder *MockInteractorMockRecorder +} + +// MockInteractorMockRecorder is the mock recorder for MockInteractor. +type MockInteractorMockRecorder struct { + mock *MockInteractor +} + +// NewMockInteractor creates a new mock instance. +func NewMockInteractor(ctrl *gomock.Controller) *MockInteractor { + mock := &MockInteractor{ctrl: ctrl} + mock.recorder = &MockInteractorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInteractor) EXPECT() *MockInteractorMockRecorder { + return m.recorder +} + +// DeletePermsOfUserLessThanUpdatedAt mocks base method. +func (m *MockInteractor) DeletePermsOfUserLessThanUpdatedAt(ctx context.Context, u *ent.User, t time.Time) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePermsOfUserLessThanUpdatedAt", ctx, u, t) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeletePermsOfUserLessThanUpdatedAt indicates an expected call of DeletePermsOfUserLessThanUpdatedAt. +func (mr *MockInteractorMockRecorder) DeletePermsOfUserLessThanUpdatedAt(ctx, u, t interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePermsOfUserLessThanUpdatedAt", reflect.TypeOf((*MockInteractor)(nil).DeletePermsOfUserLessThanUpdatedAt), ctx, u, t) +} + +// IsEntryRepo mocks base method. +func (m *MockInteractor) IsEntryRepo(ctx context.Context, namespace string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsEntryRepo", ctx, namespace) + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsEntryRepo indicates an expected call of IsEntryRepo. +func (mr *MockInteractorMockRecorder) IsEntryRepo(ctx, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEntryRepo", reflect.TypeOf((*MockInteractor)(nil).IsEntryRepo), ctx, namespace) +} + +// ListRemoteRepos mocks base method. +func (m *MockInteractor) ListRemoteRepos(ctx context.Context, u *ent.User) ([]*vo.RemoteRepo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListRemoteRepos", ctx, u) + ret0, _ := ret[0].([]*vo.RemoteRepo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListRemoteRepos indicates an expected call of ListRemoteRepos. +func (mr *MockInteractorMockRecorder) ListRemoteRepos(ctx, u interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRemoteRepos", reflect.TypeOf((*MockInteractor)(nil).ListRemoteRepos), ctx, u) +} + +// SyncRemoteRepo mocks base method. +func (m *MockInteractor) SyncRemoteRepo(ctx context.Context, u *ent.User, re *vo.RemoteRepo) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncRemoteRepo", ctx, u, re) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncRemoteRepo indicates an expected call of SyncRemoteRepo. +func (mr *MockInteractorMockRecorder) SyncRemoteRepo(ctx, u, re interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncRemoteRepo", reflect.TypeOf((*MockInteractor)(nil).SyncRemoteRepo), ctx, u, re) +} diff --git a/internal/server/api/v1/sync/syncher_test.go b/internal/server/api/v1/sync/syncher_test.go new file mode 100644 index 00000000..642655e9 --- /dev/null +++ b/internal/server/api/v1/sync/syncher_test.go @@ -0,0 +1,82 @@ +package sync + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/golang/mock/gomock" + "github.com/hanjunlee/gitploy/ent" + "github.com/hanjunlee/gitploy/internal/server/api/v1/sync/mock" + "github.com/hanjunlee/gitploy/internal/server/global" + "github.com/hanjunlee/gitploy/vo" +) + +func TestSyncher_Sync(t *testing.T) { + ctx := gomock.Any() + + t.Run("Synchronize with remote repositories", func(t *testing.T) { + ctrl := gomock.NewController(t) + m := mock.NewMockInteractor(ctrl) + + t.Log("List remote repositories") + m. + EXPECT(). + ListRemoteRepos(ctx, gomock.AssignableToTypeOf(&ent.User{})). + Return([]*vo.RemoteRepo{ + { + ID: "1", + Namespace: "octocat", + Name: "HelloWorld", + }, + { + ID: "1", + Namespace: "cat", + Name: "GoodBye", + }, + }, nil) + + t.Log("Only octocat is trusted namespace.") + m. + EXPECT(). + IsEntryRepo(ctx, gomock.Any()). + DoAndReturn(func(ctx context.Context, namespace string) bool { + return namespace == "octocat" + }). + AnyTimes() + + t.Log("Sync with HelloWorld only.") + m. + EXPECT(). + SyncRemoteRepo(ctx, gomock.Any(), gomock.Eq(&vo.RemoteRepo{ + ID: "1", + Namespace: "octocat", + Name: "HelloWorld", + })). + Return(nil) + + t.Log("Delete staled perms.") + m. + EXPECT(). + DeletePermsOfUserLessThanUpdatedAt(ctx, gomock.Any(), gomock.Any()). + Return(0, nil) + + gin.SetMode(gin.ReleaseMode) + router := gin.New() + + s := NewSyncher(m) + router.POST("/sync", func(c *gin.Context) { + c.Set(global.KeyUser, &ent.User{}) + }, s.Sync) + + req, _ := http.NewRequest("POST", "/sync", nil) + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + if w.Code != http.StatusOK { + t.Fatalf("Sync = %v, wanted %v", w.Code, http.StatusOK) + } + }) +} From 65e3724693ee9ba91d7baf52d39442cd252ea232 Mon Sep 17 00:00:00 2001 From: noah Date: Tue, 24 Aug 2021 23:20:13 +0900 Subject: [PATCH 3/4] feat: add doc --- docs/references/GITPLOY_REPOSITORY_ENTRIES.md | 7 +++++++ docs/references/index.md | 1 + 2 files changed, 8 insertions(+) create mode 100644 docs/references/GITPLOY_REPOSITORY_ENTRIES.md diff --git a/docs/references/GITPLOY_REPOSITORY_ENTRIES.md b/docs/references/GITPLOY_REPOSITORY_ENTRIES.md new file mode 100644 index 00000000..fbb9b353 --- /dev/null +++ b/docs/references/GITPLOY_REPOSITORY_ENTRIES.md @@ -0,0 +1,7 @@ +# GITPLOY_REPOSITORY_ENTRIES + +Optional comma-separated list of accounts, used to limit which repositories are syncronized between your source control management system and Gitploy. *Note that this variable must be set before your first login. If you don't set this environment it synchronize with all repositories the user owns. + +``` +GITPLOY_REPOSITORY_ENTRIES=octocat,gitploy-io +``` diff --git a/docs/references/index.md b/docs/references/index.md index 6246ad9a..c700c3b3 100644 --- a/docs/references/index.md +++ b/docs/references/index.md @@ -1,4 +1,5 @@ # Reference * [GITPLOY_SERVER_HOST](./GITPLOY_SERVER_HOST.md) +* [GITPLOY_REPOSITORY_ENTRIES](./GITPLOY_REPOSITORY_ENTRIES.md) * [GITPLOY_LICENSE](./GITPLOY_LICENSE.md) \ No newline at end of file From c0555903e9503d7f8a71ed65138af3631e69b32d Mon Sep 17 00:00:00 2001 From: noah Date: Tue, 24 Aug 2021 23:29:52 +0900 Subject: [PATCH 4/4] fix: replace env name --- cmd/server/config.go | 4 ++-- cmd/server/main.go | 2 +- docs/references/GITPLOY_ORGANIZATION_ENTRIES.md | 7 +++++++ docs/references/GITPLOY_REPOSITORY_ENTRIES.md | 7 ------- docs/references/index.md | 2 +- internal/interactor/interactor.go | 8 ++++---- internal/interactor/sync.go | 6 +++--- internal/server/api/v1/sync/interface.go | 2 +- internal/server/api/v1/sync/mock/interactor.go | 12 ++++++------ internal/server/api/v1/sync/syncher.go | 2 +- internal/server/api/v1/sync/syncher_test.go | 2 +- 11 files changed, 27 insertions(+), 27 deletions(-) create mode 100644 docs/references/GITPLOY_ORGANIZATION_ENTRIES.md delete mode 100644 docs/references/GITPLOY_REPOSITORY_ENTRIES.md diff --git a/cmd/server/config.go b/cmd/server/config.go index 920e1525..3a7b693e 100644 --- a/cmd/server/config.go +++ b/cmd/server/config.go @@ -24,8 +24,8 @@ type ( ServerProxyProto string `default:"https" split_words:"true"` ServerProxyPort string `default:"8081" split_words:"true"` - RepositoryEntries []string `split_words:"true"` - AdminUsers []string `split_words:"true"` + OrganizationEntries []string `split_words:"true"` + AdminUsers []string `split_words:"true"` License string `split_words:"true"` } diff --git a/cmd/server/main.go b/cmd/server/main.go index f7d333c9..8e8ad7d1 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -118,7 +118,7 @@ func NewInteractor(c *Config) server.Interactor { &interactor.InteractorConfig{ ServerHost: c.ServerHost, ServerProto: c.ServerProto, - RepoEntries: c.RepositoryEntries, + OrgEntries: c.OrganizationEntries, AdminUsers: c.AdminUsers, LicenseKey: c.License, Store: newStore(c), diff --git a/docs/references/GITPLOY_ORGANIZATION_ENTRIES.md b/docs/references/GITPLOY_ORGANIZATION_ENTRIES.md new file mode 100644 index 00000000..4380f87c --- /dev/null +++ b/docs/references/GITPLOY_ORGANIZATION_ENTRIES.md @@ -0,0 +1,7 @@ +# GITPLOY_ORGANIZATION_ENTRIES + +Optional comma-separated list of accounts, used to limit which organization(or owner) are syncronized between your source control management system and Gitploy. *Note that this variable must be set before your first login. If you don't set this environment it synchronize with all repositories the user owns. + +``` +GITPLOY_ORGANIZATION_ENTRIES=gitploy-io,octocat,facebook +``` diff --git a/docs/references/GITPLOY_REPOSITORY_ENTRIES.md b/docs/references/GITPLOY_REPOSITORY_ENTRIES.md deleted file mode 100644 index fbb9b353..00000000 --- a/docs/references/GITPLOY_REPOSITORY_ENTRIES.md +++ /dev/null @@ -1,7 +0,0 @@ -# GITPLOY_REPOSITORY_ENTRIES - -Optional comma-separated list of accounts, used to limit which repositories are syncronized between your source control management system and Gitploy. *Note that this variable must be set before your first login. If you don't set this environment it synchronize with all repositories the user owns. - -``` -GITPLOY_REPOSITORY_ENTRIES=octocat,gitploy-io -``` diff --git a/docs/references/index.md b/docs/references/index.md index c700c3b3..ecfcc7c4 100644 --- a/docs/references/index.md +++ b/docs/references/index.md @@ -1,5 +1,5 @@ # Reference * [GITPLOY_SERVER_HOST](./GITPLOY_SERVER_HOST.md) -* [GITPLOY_REPOSITORY_ENTRIES](./GITPLOY_REPOSITORY_ENTRIES.md) +* [GITPLOY_ORGANIZATION_ENTRIES](./GITPLOY_ORGANIZATION_ENTRIES.md) * [GITPLOY_LICENSE](./GITPLOY_LICENSE.md) \ No newline at end of file diff --git a/internal/interactor/interactor.go b/internal/interactor/interactor.go index 7019aa4c..e2c3b052 100644 --- a/internal/interactor/interactor.go +++ b/internal/interactor/interactor.go @@ -11,7 +11,7 @@ type ( ServerHost string ServerProto string - repoEntries []string + orgEntries []string // Admin Users admins []string @@ -32,8 +32,8 @@ type ( ServerHost string ServerProto string - RepoEntries []string - AdminUsers []string + OrgEntries []string + AdminUsers []string LicenseKey string @@ -46,7 +46,7 @@ func NewInteractor(c *InteractorConfig) *Interactor { i := &Interactor{ ServerHost: c.ServerHost, ServerProto: c.ServerProto, - repoEntries: c.RepoEntries, + orgEntries: c.OrgEntries, admins: c.AdminUsers, licenseKey: c.LicenseKey, Store: c.Store, diff --git a/internal/interactor/sync.go b/internal/interactor/sync.go index 0cb60201..15c29cbc 100644 --- a/internal/interactor/sync.go +++ b/internal/interactor/sync.go @@ -8,12 +8,12 @@ import ( "github.com/hanjunlee/gitploy/vo" ) -func (i *Interactor) IsEntryRepo(ctx context.Context, namespace string) bool { - if i.repoEntries == nil { +func (i *Interactor) IsEntryOrg(ctx context.Context, namespace string) bool { + if i.orgEntries == nil { return true } - for _, r := range i.repoEntries { + for _, r := range i.orgEntries { if namespace == r { return true } diff --git a/internal/server/api/v1/sync/interface.go b/internal/server/api/v1/sync/interface.go index d35412a8..79371cf5 100644 --- a/internal/server/api/v1/sync/interface.go +++ b/internal/server/api/v1/sync/interface.go @@ -13,7 +13,7 @@ import ( type ( Interactor interface { ListRemoteRepos(ctx context.Context, u *ent.User) ([]*vo.RemoteRepo, error) - IsEntryRepo(ctx context.Context, namespace string) bool + IsEntryOrg(ctx context.Context, namespace string) bool SyncRemoteRepo(ctx context.Context, u *ent.User, re *vo.RemoteRepo) error DeletePermsOfUserLessThanUpdatedAt(ctx context.Context, u *ent.User, t time.Time) (int, error) } diff --git a/internal/server/api/v1/sync/mock/interactor.go b/internal/server/api/v1/sync/mock/interactor.go index 863e2e4e..2c3c5a64 100644 --- a/internal/server/api/v1/sync/mock/interactor.go +++ b/internal/server/api/v1/sync/mock/interactor.go @@ -52,18 +52,18 @@ func (mr *MockInteractorMockRecorder) DeletePermsOfUserLessThanUpdatedAt(ctx, u, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePermsOfUserLessThanUpdatedAt", reflect.TypeOf((*MockInteractor)(nil).DeletePermsOfUserLessThanUpdatedAt), ctx, u, t) } -// IsEntryRepo mocks base method. -func (m *MockInteractor) IsEntryRepo(ctx context.Context, namespace string) bool { +// IsEntryOrg mocks base method. +func (m *MockInteractor) IsEntryOrg(ctx context.Context, namespace string) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsEntryRepo", ctx, namespace) + ret := m.ctrl.Call(m, "IsEntryOrg", ctx, namespace) ret0, _ := ret[0].(bool) return ret0 } -// IsEntryRepo indicates an expected call of IsEntryRepo. -func (mr *MockInteractorMockRecorder) IsEntryRepo(ctx, namespace interface{}) *gomock.Call { +// IsEntryOrg indicates an expected call of IsEntryOrg. +func (mr *MockInteractorMockRecorder) IsEntryOrg(ctx, namespace interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEntryRepo", reflect.TypeOf((*MockInteractor)(nil).IsEntryRepo), ctx, namespace) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEntryOrg", reflect.TypeOf((*MockInteractor)(nil).IsEntryOrg), ctx, namespace) } // ListRemoteRepos mocks base method. diff --git a/internal/server/api/v1/sync/syncher.go b/internal/server/api/v1/sync/syncher.go index adb3e8a8..0e95e324 100644 --- a/internal/server/api/v1/sync/syncher.go +++ b/internal/server/api/v1/sync/syncher.go @@ -47,7 +47,7 @@ func (s *Syncher) Sync(c *gin.Context) { syncCnt := 0 for _, re := range remotes { // Skip un-selected repositories. - if !s.i.IsEntryRepo(ctx, re.Namespace) { + if !s.i.IsEntryOrg(ctx, re.Namespace) { continue } diff --git a/internal/server/api/v1/sync/syncher_test.go b/internal/server/api/v1/sync/syncher_test.go index 642655e9..9f013408 100644 --- a/internal/server/api/v1/sync/syncher_test.go +++ b/internal/server/api/v1/sync/syncher_test.go @@ -41,7 +41,7 @@ func TestSyncher_Sync(t *testing.T) { t.Log("Only octocat is trusted namespace.") m. EXPECT(). - IsEntryRepo(ctx, gomock.Any()). + IsEntryOrg(ctx, gomock.Any()). DoAndReturn(func(ctx context.Context, namespace string) bool { return namespace == "octocat" }).