From 61751f5bace08de1ea5fb0f5711860f61a8923ca Mon Sep 17 00:00:00 2001 From: noah Date: Thu, 28 Oct 2021 22:24:47 +0900 Subject: [PATCH 1/5] Add OSS license --- internal/server/api/shared/middleware.go | 6 ++++- internal/server/api/shared/middleware_test.go | 27 +++++++++++++++++++ vo/license.go | 23 +++++++++++++++- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/internal/server/api/shared/middleware.go b/internal/server/api/shared/middleware.go index 574f8057..4a1fdca8 100644 --- a/internal/server/api/shared/middleware.go +++ b/internal/server/api/shared/middleware.go @@ -34,12 +34,16 @@ func (m *Middleware) IsLicenseExpired() gin.HandlerFunc { return } + if lic.IsOSS() { + return + } + if lic.IsOverLimit() { gb.AbortWithErrorResponse(c, http.StatusPaymentRequired, "The member count is over the limit.") return } - if !lic.IsTrial() && lic.IsExpired() { + if lic.IsStandard() && lic.IsExpired() { now := time.Now() if lic.ExpiredAt.Add(extraDuration).Before(now) { gb.AbortWithErrorResponse(c, http.StatusPaymentRequired, "The license is expired.") diff --git a/internal/server/api/shared/middleware_test.go b/internal/server/api/shared/middleware_test.go index 92720aae..c7a3a2b6 100644 --- a/internal/server/api/shared/middleware_test.go +++ b/internal/server/api/shared/middleware_test.go @@ -15,6 +15,33 @@ import ( func TestMiddleware_IsLicenseExpired(t *testing.T) { month := 30 * 24 * time.Hour + t.Run("Return 200 when the license is OSS.", func(t *testing.T) { + ctrl := gomock.NewController(t) + m := mock.NewMockInteractor(ctrl) + + m. + EXPECT(). + GetLicense(gomock.Any()). + Return(vo.NewOSSLicense(), nil) + + gin.SetMode(gin.ReleaseMode) + router := gin.New() + + lm := NewMiddleware(m) + router.GET("/repos", lm.IsLicenseExpired(), func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + req, _ := http.NewRequest("GET", "/repos", nil) + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("IsLicenseExpired = %v, wanted %v", w.Code, http.StatusOK) + } + }) + t.Run("Return 402 error when the count of member is over the limit.", func(t *testing.T) { ctrl := gomock.NewController(t) m := mock.NewMockInteractor(ctrl) diff --git a/vo/license.go b/vo/license.go index 77411674..047bfe83 100644 --- a/vo/license.go +++ b/vo/license.go @@ -7,7 +7,11 @@ const ( ) const ( - LicenseKindTrial LicenseKind = "trial" + // LicenseKindOSS is a license for the community edition. + LicenseKindOSS LicenseKind = "oss" + // LicenseKindTrial is a trial license of the enterprise edition. + LicenseKindTrial LicenseKind = "trial" + // LicenseKindStandard is a license of the enterprise edition. LicenseKindStandard LicenseKind = "standard" ) @@ -28,6 +32,13 @@ type ( } ) +func NewOSSLicense() *License { + return &License{ + Kind: LicenseKindOSS, + MemberCount: -1, + } +} + func NewTrialLicense(cnt int) *License { return &License{ Kind: LicenseKindTrial, @@ -45,14 +56,24 @@ func NewStandardLicense(cnt int, d *SigningData) *License { } } +func (l *License) IsOSS() bool { + return l.Kind == LicenseKindOSS +} + func (l *License) IsTrial() bool { return l.Kind == LicenseKindTrial } +func (l *License) IsStandard() bool { + return l.Kind == LicenseKindStandard +} + +// IsOverLimit verify it is over the limit of the license. func (l *License) IsOverLimit() bool { return l.MemberCount > l.MemberLimit } +// IsExpired verify that the license is expired or not. func (l *License) IsExpired() bool { return l.ExpiredAt.Before(time.Now()) } From 5bd6727a60dd34b16596100b6a1963d396fa9aa5 Mon Sep 17 00:00:00 2001 From: noah Date: Thu, 28 Oct 2021 22:38:39 +0900 Subject: [PATCH 2/5] Add a test for 'license.go' --- internal/interactor/license_test.go | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 internal/interactor/license_test.go diff --git a/internal/interactor/license_test.go b/internal/interactor/license_test.go new file mode 100644 index 00000000..172c7ec7 --- /dev/null +++ b/internal/interactor/license_test.go @@ -0,0 +1,34 @@ +package interactor + +import ( + "context" + "testing" + + "github.com/gitploy-io/gitploy/internal/interactor/mock" + "github.com/gitploy-io/gitploy/vo" + "github.com/golang/mock/gomock" +) + +func TestStore_GetLicense(t *testing.T) { + t.Run("Return the trial license when the signing data is nil.", func(t *testing.T) { + ctrl := gomock.NewController(t) + store := mock.NewMockStore(ctrl) + + t.Log("MOCK - return the count of users.") + store. + EXPECT(). + CountUsers(gomock.AssignableToTypeOf(context.Background())). + Return(5, nil) + + i := &Interactor{Store: store} + + lic, err := i.GetLicense(context.Background()) + if err != nil { + t.Fatalf("GetLicense returns an error: %s", err) + } + + if !lic.IsTrial() { + t.Fatalf("GetLicense = %v, wanted %v", lic.Kind, vo.LicenseKindTrial) + } + }) +} From 82618e54046f4bbf431a406a1e0ebc58a808e678 Mon Sep 17 00:00:00 2001 From: noah Date: Thu, 28 Oct 2021 22:43:07 +0900 Subject: [PATCH 3/5] Add license file of OSS version --- internal/interactor/license.go | 6 ++++++ internal/interactor/license_oss.go | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 internal/interactor/license_oss.go diff --git a/internal/interactor/license.go b/internal/interactor/license.go index 11451aad..6d6a1a79 100644 --- a/internal/interactor/license.go +++ b/internal/interactor/license.go @@ -1,3 +1,9 @@ +// Copyright 2021 Gitploy.IO Inc. All rights reserved. +// Use of this source code is governed by the Gitploy Non-Commercial License +// that can be found in the LICENSE file. + +// +build !oss + package interactor import ( diff --git a/internal/interactor/license_oss.go b/internal/interactor/license_oss.go new file mode 100644 index 00000000..1b4216a6 --- /dev/null +++ b/internal/interactor/license_oss.go @@ -0,0 +1,13 @@ +// +build oss + +package interactor + +import ( + "context" + + "github.com/gitploy-io/gitploy/vo" +) + +func (i *Interactor) GetLicense(ctx context.Context) (*vo.License, error) { + return vo.NewOSSLicense(), nil +} From ca40be87e21c803841d5b1bb7377f18acd6dfbf5 Mon Sep 17 00:00:00 2001 From: noah Date: Thu, 28 Oct 2021 23:05:07 +0900 Subject: [PATCH 4/5] Add limit count of deployment. --- internal/interactor/interface.go | 1 + internal/interactor/license.go | 14 ++++--- internal/interactor/license_test.go | 7 +++- internal/interactor/mock/pkg.go | 15 +++++++ internal/pkg/store/deployment.go | 6 +++ internal/server/api/shared/middleware_test.go | 4 +- vo/license.go | 41 +++++++++++-------- vo/license_test.go | 35 ++++++++++++---- 8 files changed, 91 insertions(+), 32 deletions(-) diff --git a/internal/interactor/interface.go b/internal/interactor/interface.go index 1dbbea6d..44cb915a 100644 --- a/internal/interactor/interface.go +++ b/internal/interactor/interface.go @@ -45,6 +45,7 @@ type ( UpdatePerm(ctx context.Context, p *ent.Perm) (*ent.Perm, error) DeletePermsOfUserLessThanSyncedAt(ctx context.Context, u *ent.User, t time.Time) (int, error) + CountDeployments(ctx context.Context) (int, error) SearchDeployments(ctx context.Context, u *ent.User, s []deployment.Status, owned bool, from time.Time, to time.Time, page, perPage int) ([]*ent.Deployment, error) ListInactiveDeploymentsLessThanTime(ctx context.Context, t time.Time, page, perPage int) ([]*ent.Deployment, error) ListDeploymentsOfRepo(ctx context.Context, r *ent.Repo, env string, status string, page, perPage int) ([]*ent.Deployment, error) diff --git a/internal/interactor/license.go b/internal/interactor/license.go index 6d6a1a79..0831adca 100644 --- a/internal/interactor/license.go +++ b/internal/interactor/license.go @@ -16,9 +16,10 @@ import ( func (i *Interactor) GetLicense(ctx context.Context) (*vo.License, error) { var ( - cnt int - d *vo.SigningData - err error + memberCnt int + deploymentCnt int + d *vo.SigningData + err error ) if cnt, err = i.Store.CountUsers(ctx); err != nil { @@ -26,10 +27,13 @@ func (i *Interactor) GetLicense(ctx context.Context) (*vo.License, error) { e.ErrorCodeInternalError, err, ) + + if deploymentCnt, err = i.Store.CountDeployments(ctx); err != nil { + return nil, err } if i.licenseKey == "" { - lic := vo.NewTrialLicense(cnt) + lic := vo.NewTrialLicense(memberCnt, deploymentCnt) return lic, nil } @@ -40,6 +44,6 @@ func (i *Interactor) GetLicense(ctx context.Context) (*vo.License, error) { ) } - lic := vo.NewStandardLicense(cnt, d) + lic := vo.NewStandardLicense(memberCnt, d) return lic, nil } diff --git a/internal/interactor/license_test.go b/internal/interactor/license_test.go index 172c7ec7..fff5ccf2 100644 --- a/internal/interactor/license_test.go +++ b/internal/interactor/license_test.go @@ -18,7 +18,12 @@ func TestStore_GetLicense(t *testing.T) { store. EXPECT(). CountUsers(gomock.AssignableToTypeOf(context.Background())). - Return(5, nil) + Return(vo.TrialMemberLimit, nil) + + store. + EXPECT(). + CountDeployments(gomock.AssignableToTypeOf(context.Background())). + Return(vo.TrialDeploymentLimit, nil) i := &Interactor{Store: store} diff --git a/internal/interactor/mock/pkg.go b/internal/interactor/mock/pkg.go index d186ed1f..940bb143 100644 --- a/internal/interactor/mock/pkg.go +++ b/internal/interactor/mock/pkg.go @@ -83,6 +83,21 @@ func (mr *MockStoreMockRecorder) CountActiveRepos(ctx interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountActiveRepos", reflect.TypeOf((*MockStore)(nil).CountActiveRepos), ctx) } +// CountDeployments mocks base method. +func (m *MockStore) CountDeployments(ctx context.Context) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CountDeployments", ctx) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CountDeployments indicates an expected call of CountDeployments. +func (mr *MockStoreMockRecorder) CountDeployments(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountDeployments", reflect.TypeOf((*MockStore)(nil).CountDeployments), ctx) +} + // CountRepos mocks base method. func (m *MockStore) CountRepos(ctx context.Context) (int, error) { m.ctrl.T.Helper() diff --git a/internal/pkg/store/deployment.go b/internal/pkg/store/deployment.go index d57eca42..bca427d3 100644 --- a/internal/pkg/store/deployment.go +++ b/internal/pkg/store/deployment.go @@ -11,6 +11,12 @@ import ( "github.com/gitploy-io/gitploy/ent/predicate" ) +func (s *Store) CountDeployments(ctx context.Context) (int, error) { + return s.c.Deployment. + Query(). + Count(ctx) +} + func (s *Store) SearchDeployments(ctx context.Context, u *ent.User, ss []deployment.Status, owned bool, from time.Time, to time.Time, page, perPage int) ([]*ent.Deployment, error) { statusIn := func(ss []deployment.Status) predicate.Deployment { if len(ss) == 0 { diff --git a/internal/server/api/shared/middleware_test.go b/internal/server/api/shared/middleware_test.go index c7a3a2b6..a23c89fb 100644 --- a/internal/server/api/shared/middleware_test.go +++ b/internal/server/api/shared/middleware_test.go @@ -49,7 +49,7 @@ func TestMiddleware_IsLicenseExpired(t *testing.T) { m. EXPECT(). GetLicense(gomock.Any()). - Return(vo.NewTrialLicense(7), nil) + Return(vo.NewTrialLicense(vo.TrialMemberLimit+1, vo.TrialDeploymentLimit), nil) gin.SetMode(gin.ReleaseMode) router := gin.New() @@ -76,7 +76,7 @@ func TestMiddleware_IsLicenseExpired(t *testing.T) { m. EXPECT(). GetLicense(gomock.Any()). - Return(vo.NewTrialLicense(vo.TrialMemberLimit), nil) + Return(vo.NewTrialLicense(vo.TrialMemberLimit, vo.TrialDeploymentLimit), nil) gin.SetMode(gin.ReleaseMode) router := gin.New() diff --git a/vo/license.go b/vo/license.go index 047bfe83..f001f044 100644 --- a/vo/license.go +++ b/vo/license.go @@ -3,7 +3,8 @@ package vo import "time" const ( - TrialMemberLimit = 5 + TrialMemberLimit = 5 + TrialDeploymentLimit = 5000 ) const ( @@ -19,10 +20,12 @@ type ( LicenseKind string License struct { - Kind LicenseKind `json:"kind"` - MemberCount int `json:"member_count"` - MemberLimit int `json:"memeber_limit"` - ExpiredAt time.Time `json:"expired_at"` + Kind LicenseKind `json:"kind"` + MemberCount int `json:"member_count"` + MemberLimit int `json:"memeber_limit"` + DeploymentCount int `json:"deployment_count"` + DeploymentLimit int `json:"deployment_limit"` + ExpiredAt time.Time `json:"expired_at"` } // SigningData marshal and unmarshal the content of license. @@ -34,25 +37,29 @@ type ( func NewOSSLicense() *License { return &License{ - Kind: LicenseKindOSS, - MemberCount: -1, + Kind: LicenseKindOSS, + MemberCount: -1, + DeploymentCount: -1, } } -func NewTrialLicense(cnt int) *License { +func NewTrialLicense(memberCnt, deploymentCnt int) *License { return &License{ - Kind: LicenseKindTrial, - MemberCount: cnt, - MemberLimit: TrialMemberLimit, + Kind: LicenseKindTrial, + MemberCount: memberCnt, + MemberLimit: TrialMemberLimit, + DeploymentCount: deploymentCnt, + DeploymentLimit: TrialDeploymentLimit, } } -func NewStandardLicense(cnt int, d *SigningData) *License { +func NewStandardLicense(memberCnt int, d *SigningData) *License { return &License{ - Kind: LicenseKindStandard, - MemberCount: cnt, - MemberLimit: d.MemberLimit, - ExpiredAt: d.ExpiredAt, + Kind: LicenseKindStandard, + MemberCount: memberCnt, + MemberLimit: d.MemberLimit, + DeploymentCount: -1, + ExpiredAt: d.ExpiredAt, } } @@ -70,7 +77,7 @@ func (l *License) IsStandard() bool { // IsOverLimit verify it is over the limit of the license. func (l *License) IsOverLimit() bool { - return l.MemberCount > l.MemberLimit + return l.MemberCount > l.MemberLimit || l.DeploymentCount > l.DeploymentLimit } // IsExpired verify that the license is expired or not. diff --git a/vo/license_test.go b/vo/license_test.go index 1c165eff..41599fc1 100644 --- a/vo/license_test.go +++ b/vo/license_test.go @@ -6,8 +6,17 @@ import ( ) func TestLicense_IsOverLimit(t *testing.T) { - t.Run("Return false when the count of member is over the limit.", func(t *testing.T) { - l := NewTrialLicense(6) + t.Run("Return false when the license is OSS.", func(t *testing.T) { + l := NewOSSLicense() + + expected := false + if finished := l.IsOverLimit(); finished != expected { + t.Fatalf("IsOverLimit = %v, wanted %v", finished, expected) + } + }) + + t.Run("Return true when the trial license is over the member limit.", func(t *testing.T) { + l := NewTrialLicense(TrialMemberLimit+1, 0) expected := true if finished := l.IsOverLimit(); finished != expected { @@ -15,14 +24,26 @@ func TestLicense_IsOverLimit(t *testing.T) { } }) - t.Run("Return true when the count of member is under the limit.", func(t *testing.T) { - tl := NewTrialLicense(5) + t.Run("Return true when the trial license is over the deployment limit.", func(t *testing.T) { + l := NewTrialLicense(5, TrialDeploymentLimit+1) - if finished := tl.IsOverLimit(); finished != false { - t.Fatalf("IsOverLimit = %v, wanted %v", finished, false) + expected := true + if finished := l.IsOverLimit(); finished != expected { + t.Fatalf("IsOverLimit = %v, wanted %v", finished, expected) } + }) + + t.Run("Return false when the trial license is less than or equal to the limit.", func(t *testing.T) { + l := NewTrialLicense(TrialMemberLimit, TrialDeploymentLimit) + + expected := false + if finished := l.IsOverLimit(); finished != expected { + t.Fatalf("IsOverLimit = %v, wanted %v", finished, expected) + } + }) - sl := NewStandardLicense(10, &SigningData{ + t.Run("Return true when the standard license is less than the limit.", func(t *testing.T) { + sl := NewStandardLicense(20, &SigningData{ MemberLimit: 20, ExpiredAt: time.Now(), }) From 9407d16f2cd4753997530a1e17ccb1e78c87eec7 Mon Sep 17 00:00:00 2001 From: noah Date: Fri, 29 Oct 2021 21:50:09 +0900 Subject: [PATCH 5/5] Fix returned error --- internal/interactor/license.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/interactor/license.go b/internal/interactor/license.go index 0831adca..ef6f482f 100644 --- a/internal/interactor/license.go +++ b/internal/interactor/license.go @@ -22,14 +22,18 @@ func (i *Interactor) GetLicense(ctx context.Context) (*vo.License, error) { err error ) - if cnt, err = i.Store.CountUsers(ctx); err != nil { + if memberCnt, err = i.Store.CountUsers(ctx); err != nil { return nil, e.NewError( e.ErrorCodeInternalError, err, ) + } if deploymentCnt, err = i.Store.CountDeployments(ctx); err != nil { - return nil, err + return nil, e.NewError( + e.ErrorCodeInternalError, + err, + ) } if i.licenseKey == "" {