diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d039294..b8015d1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added - added nsswitch to docker images +- option to auto-cancel pending builds when newer build enqueued, by [@bradrydzewski](https://github.com/bradrydzewski). [#1980](https://github.com/drone/drone/issues/1980). ## [1.5.1] - 2019-09-30 ### Added diff --git a/cmd/drone-server/inject_service.go b/cmd/drone-server/inject_service.go index d25a7795..f293ca1d 100644 --- a/cmd/drone-server/inject_service.go +++ b/cmd/drone-server/inject_service.go @@ -22,6 +22,7 @@ import ( "github.com/drone/drone/livelog" "github.com/drone/drone/metric/sink" "github.com/drone/drone/pubsub" + "github.com/drone/drone/service/canceler" "github.com/drone/drone/service/commit" contents "github.com/drone/drone/service/content" "github.com/drone/drone/service/content/cache" @@ -46,6 +47,7 @@ import ( // wire set for loading the services. var serviceSet = wire.NewSet( + canceler.New, commit.New, cron.New, livelog.New, diff --git a/cmd/drone-server/wire_gen.go b/cmd/drone-server/wire_gen.go index 40a684ca..a6b953e8 100644 --- a/cmd/drone-server/wire_gen.go +++ b/cmd/drone-server/wire_gen.go @@ -12,6 +12,7 @@ import ( "github.com/drone/drone/livelog" "github.com/drone/drone/operator/manager" "github.com/drone/drone/pubsub" + "github.com/drone/drone/service/canceler" "github.com/drone/drone/service/commit" "github.com/drone/drone/service/hook/parser" "github.com/drone/drone/service/license" @@ -47,17 +48,19 @@ func InitializeApplication(config2 config.Config) (application, error) { commitService := commit.New(client, renewer) cronStore := cron.New(db) repositoryStore := provideRepoStore(db) - fileService := provideContentService(client, renewer) - configService := provideConfigPlugin(client, fileService, config2) - convertService := provideConvertPlugin(client, config2) - statusService := provideStatusService(client, renewer, config2) buildStore := provideBuildStore(db) stageStore := provideStageStore(db) scheduler := provideScheduler(stageStore, config2) - validateService := provideValidatePlugin(config2) + statusService := provideStatusService(client, renewer, config2) + stepStore := step.New(db) system := provideSystem(config2) webhookSender := provideWebhookPlugin(config2, system) - triggerer := trigger.New(configService, convertService, commitService, statusService, buildStore, scheduler, repositoryStore, userStore, validateService, webhookSender) + coreCanceler := canceler.New(buildStore, repositoryStore, scheduler, stageStore, statusService, stepStore, userStore, webhookSender) + fileService := provideContentService(client, renewer) + configService := provideConfigPlugin(client, fileService, config2) + convertService := provideConvertPlugin(client, config2) + validateService := provideValidatePlugin(config2) + triggerer := trigger.New(coreCanceler, configService, convertService, commitService, statusService, buildStore, scheduler, repositoryStore, userStore, validateService, webhookSender) cronScheduler := cron2.New(commitService, cronStore, repositoryStore, userStore, triggerer) coreLicense := provideLicense(client, config2) datadog := provideDatadog(userStore, repositoryStore, buildStore, system, coreLicense, config2) @@ -71,7 +74,6 @@ func InitializeApplication(config2 config.Config) (application, error) { } secretStore := secret.New(db, encrypter) globalSecretStore := global.New(db, encrypter) - stepStore := step.New(db) buildManager := manager.New(buildStore, configService, convertService, corePubsub, logStore, logStream, netrcService, repositoryStore, scheduler, secretStore, globalSecretStore, statusService, stageStore, stepStore, system, userStore, webhookSender) secretService := provideSecretPlugin(config2) registryService := provideRegistryPlugin(config2) diff --git a/core/repo.go b/core/repo.go index f3bf62a7..cb2b5048 100644 --- a/core/repo.go +++ b/core/repo.go @@ -52,7 +52,7 @@ type ( IgnoreForks bool `json:"ignore_forks"` IgnorePulls bool `json:"ignore_pull_requests"` CancelPulls bool `json:"auto_cancel_pull_requests"` - CancelPush bool `json:"auto_cancel_branch"` + CancelPush bool `json:"auto_cancel_pushes"` Timeout int64 `json:"timeout"` Counter int64 `json:"counter"` Synced int64 `json:"synced"` diff --git a/handler/api/repos/update.go b/handler/api/repos/update.go index 079a1a1c..dad37f6e 100644 --- a/handler/api/repos/update.go +++ b/handler/api/repos/update.go @@ -34,6 +34,8 @@ type ( Protected *bool `json:"protected"` IgnoreForks *bool `json:"ignore_forks"` IgnorePulls *bool `json:"ignore_pull_requests"` + CancelPulls *bool `json:"auto_cancel_pull_requests"` + CancelPush *bool `json:"auto_cancel_pushes"` Timeout *int64 `json:"timeout"` Counter *int64 `json:"counter"` } @@ -86,6 +88,12 @@ func HandleUpdate(repos core.RepositoryStore) http.HandlerFunc { if in.IgnorePulls != nil { repo.IgnorePulls = *in.IgnorePulls } + if in.CancelPulls != nil { + repo.CancelPulls = *in.CancelPulls + } + if in.CancelPush != nil { + repo.CancelPush = *in.CancelPush + } // // system administrator only diff --git a/mock/mock.go b/mock/mock.go index ea58f925..c88088c6 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -6,4 +6,4 @@ package mock -//go:generate mockgen -package=mock -destination=mock_gen.go github.com/drone/drone/core ConvertService,ValidateService,NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,GlobalSecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Triggerer,Syncer,LogStream,WebhookSender,LicenseService +//go:generate mockgen -package=mock -destination=mock_gen.go github.com/drone/drone/core Canceler,ConvertService,ValidateService,NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,GlobalSecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Triggerer,Syncer,LogStream,WebhookSender,LicenseService diff --git a/mock/mock_gen.go b/mock/mock_gen.go index 6740fa2a..9d270ae8 100644 --- a/mock/mock_gen.go +++ b/mock/mock_gen.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/drone/drone/core (interfaces: ConvertService,ValidateService,NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,GlobalSecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Triggerer,Syncer,LogStream,WebhookSender,LicenseService) +// Source: github.com/drone/drone/core (interfaces: Canceler,ConvertService,ValidateService,NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,GlobalSecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Triggerer,Syncer,LogStream,WebhookSender,LicenseService) // Package mock is a generated GoMock package. package mock @@ -13,6 +13,57 @@ import ( reflect "reflect" ) +// MockCanceler is a mock of Canceler interface +type MockCanceler struct { + ctrl *gomock.Controller + recorder *MockCancelerMockRecorder +} + +// MockCancelerMockRecorder is the mock recorder for MockCanceler +type MockCancelerMockRecorder struct { + mock *MockCanceler +} + +// NewMockCanceler creates a new mock instance +func NewMockCanceler(ctrl *gomock.Controller) *MockCanceler { + mock := &MockCanceler{ctrl: ctrl} + mock.recorder = &MockCancelerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockCanceler) EXPECT() *MockCancelerMockRecorder { + return m.recorder +} + +// Cancel mocks base method +func (m *MockCanceler) Cancel(arg0 context.Context, arg1 *core.Repository, arg2 *core.Build) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Cancel", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Cancel indicates an expected call of Cancel +func (mr *MockCancelerMockRecorder) Cancel(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cancel", reflect.TypeOf((*MockCanceler)(nil).Cancel), arg0, arg1, arg2) +} + +// CancelPending mocks base method +func (m *MockCanceler) CancelPending(arg0 context.Context, arg1 *core.Repository, arg2 *core.Build) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CancelPending", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// CancelPending indicates an expected call of CancelPending +func (mr *MockCancelerMockRecorder) CancelPending(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelPending", reflect.TypeOf((*MockCanceler)(nil).CancelPending), arg0, arg1, arg2) +} + // MockConvertService is a mock of ConvertService interface type MockConvertService struct { ctrl *gomock.Controller diff --git a/service/canceler/canceler.go b/service/canceler/canceler.go index ecc1ac1e..59d34804 100644 --- a/service/canceler/canceler.go +++ b/service/canceler/canceler.go @@ -16,6 +16,7 @@ package canceler import ( "context" + "runtime/debug" "time" "github.com/drone/drone/core" @@ -67,6 +68,19 @@ func (s *service) Cancel(ctx context.Context, repo *core.Repository, build *core // CancelPending cancels all pending builds of the same event // and reference with lower build numbers. func (s *service) CancelPending(ctx context.Context, repo *core.Repository, build *core.Build) error { + defer func() { + if err := recover(); err != nil { + debug.PrintStack() + } + }() + + // switch { + // case repo.CancelPulls && build.Event == core.EventPullRequest: + // case repo.CancelPush && build.Event == core.EventPush: + // default: + // return nil + // } + switch build.Event { // on the push and pull request builds can be automatically // cancelled by the system. diff --git a/trigger/trigger.go b/trigger/trigger.go index e07b28c8..7f6c9e81 100644 --- a/trigger/trigger.go +++ b/trigger/trigger.go @@ -32,6 +32,7 @@ import ( ) type triggerer struct { + canceler core.Canceler config core.ConfigService convert core.ConvertService commits core.CommitService @@ -46,6 +47,7 @@ type triggerer struct { // New returns a new build triggerer. func New( + canceler core.Canceler, config core.ConfigService, convert core.ConvertService, commits core.CommitService, @@ -58,6 +60,7 @@ func New( hooks core.WebhookSender, ) core.Triggerer { return &triggerer{ + canceler: canceler, config: config, convert: convert, commits: commits, @@ -459,6 +462,12 @@ func (t *triggerer) Trigger(ctx context.Context, repo *core.Repository, base *co logger = logger.WithError(err) logger.Warnln("trigger: cannot send webhook") } + + if repo.CancelPush && build.Event == core.EventPush || + repo.CancelPulls && build.Event == core.EventPullRequest { + go t.canceler.CancelPending(ctx, repo, build) + } + // err = t.hooks.SendEndpoint(ctx, payload, repo.Endpoints.Webhook) // if err != nil { // logger.Warn().Err(err). diff --git a/trigger/trigger_test.go b/trigger/trigger_test.go index 978d5922..ff6b7efd 100644 --- a/trigger/trigger_test.go +++ b/trigger/trigger_test.go @@ -79,6 +79,7 @@ func TestTrigger(t *testing.T) { mockWebhooks.EXPECT().Send(gomock.Any(), gomock.Any()).Return(nil) triggerer := New( + nil, mockConfigService, mockConvertService, nil, @@ -115,6 +116,7 @@ func TestTrigger_SkipCI(t *testing.T) { nil, nil, nil, + nil, ) dummyHookSkip := *dummyHook dummyHookSkip.Message = "foo [CI SKIP] bar" @@ -140,6 +142,7 @@ func TestTrigger_NoOwner(t *testing.T) { nil, nil, nil, + nil, mockUsers, nil, nil, @@ -164,6 +167,7 @@ func TestTrigger_MissingYaml(t *testing.T) { mockConfigService.EXPECT().Find(gomock.Any(), gomock.Any()).Return(nil, io.EOF) triggerer := New( + nil, mockConfigService, nil, nil, @@ -204,6 +208,7 @@ func TestTrigger_ErrorYaml(t *testing.T) { mockBuilds.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) // .Do(checkBuild).Return(nil) triggerer := New( + nil, mockConfigService, mockConvertService, nil, @@ -251,6 +256,7 @@ func TestTrigger_SkipBranch(t *testing.T) { mockValidateService.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil) triggerer := New( + nil, mockConfigService, mockConvertService, nil, @@ -288,6 +294,7 @@ func TestTrigger_SkipEvent(t *testing.T) { mockValidateService.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil) triggerer := New( + nil, mockConfigService, mockConvertService, nil, @@ -325,6 +332,7 @@ func TestTrigger_SkipAction(t *testing.T) { mockValidateService.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil) triggerer := New( + nil, mockConfigService, mockConvertService, nil, @@ -366,6 +374,7 @@ func TestTrigger_ErrorIncrement(t *testing.T) { mockValidateService.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil) triggerer := New( + nil, mockConfigService, mockConvertService, nil,