added validation plugins

pull/2820/head
Brad Rydzewski 2019-09-03 15:05:53 -07:00
parent e272027814
commit 30ca88b9e6
12 changed files with 339 additions and 25 deletions

View File

@ -6,8 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- support for configuration conversion plugins (e.g. convert from starlark)
- moved native jsonnet to a conversion plugin
- support for validation plugins, by [@bradrydzewski](https://github.com/bradrydzewski). [#2266](https://github.com/drone/drone/issues/2266).
- support for conversion plugins, by [@bradrydzewski](https://github.com/bradrydzewski).
### Removed
- Support for basic auth as an option for Gitea, by [@techknowlogick](https://giteahub.com/techknowlogick). [#2721](https://github.com/drone/drone/issues/2721)

View File

@ -120,7 +120,13 @@ func provideSecretPlugin(config spec.Config) core.SecretService {
// returns a yaml validation plugin based on the environment
// configuration.
func provideValidatePlugin(conf spec.Config) core.ValidateService {
return validator.Combine()
return validator.Combine(
validator.Remote(
conf.Validate.Endpoint,
conf.Validate.Secret,
conf.Validate.SkipVerify,
),
)
}
// provideWebhookPlugin is a Wire provider function that returns

View File

@ -56,9 +56,10 @@ func InitializeApplication(config2 config.Config) (application, error) {
buildStore := provideBuildStore(db)
stageStore := provideStageStore(db)
scheduler := provideScheduler(stageStore, config2)
validateService := provideValidatePlugin(config2)
system := provideSystem(config2)
webhookSender := provideWebhookPlugin(config2, system)
triggerer := trigger.New(configService, convertService, commitService, statusService, buildStore, scheduler, repositoryStore, userStore, webhookSender)
triggerer := trigger.New(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)

View File

@ -6,4 +6,4 @@
package mock
//go:generate mockgen -package=mock -destination=mock_gen.go github.com/drone/drone/core ConvertService,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 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

View File

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/drone/drone/core (interfaces: ConvertService,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: 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
@ -51,6 +51,43 @@ func (mr *MockConvertServiceMockRecorder) Convert(arg0, arg1 interface{}) *gomoc
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Convert", reflect.TypeOf((*MockConvertService)(nil).Convert), arg0, arg1)
}
// MockValidateService is a mock of ValidateService interface
type MockValidateService struct {
ctrl *gomock.Controller
recorder *MockValidateServiceMockRecorder
}
// MockValidateServiceMockRecorder is the mock recorder for MockValidateService
type MockValidateServiceMockRecorder struct {
mock *MockValidateService
}
// NewMockValidateService creates a new mock instance
func NewMockValidateService(ctrl *gomock.Controller) *MockValidateService {
mock := &MockValidateService{ctrl: ctrl}
mock.recorder = &MockValidateServiceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockValidateService) EXPECT() *MockValidateServiceMockRecorder {
return m.recorder
}
// Validate mocks base method
func (m *MockValidateService) Validate(arg0 context.Context, arg1 *core.ValidateArgs) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Validate", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Validate indicates an expected call of Validate
func (mr *MockValidateServiceMockRecorder) Validate(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockValidateService)(nil).Validate), arg0, arg1)
}
// MockNetrcService is a mock of NetrcService interface
type MockNetrcService struct {
ctrl *gomock.Controller

View File

@ -0,0 +1,58 @@
// 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 validator
import (
"context"
"errors"
"testing"
"github.com/drone/drone/core"
"github.com/drone/drone/mock"
"github.com/golang/mock/gomock"
)
var noContext = context.Background()
var mockFile = `
kind: pipeline
type: docker
name: testing
`
func TestCombine(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
args := &core.ValidateArgs{
User: &core.User{Login: "octocat"},
Repo: &core.Repository{Slug: "octocat/hello-world", Config: ".drone.yml"},
Build: &core.Build{After: "6d144de7"},
Config: &core.Config{},
}
service := mock.NewMockValidateService(controller)
service.EXPECT().Validate(noContext, args).Return(nil)
err := Combine(service).Validate(noContext, args)
if err != nil {
t.Error(err)
}
}
func TestCombineErr(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
resp := errors.New("")
service := mock.NewMockValidateService(controller)
service.EXPECT().Validate(noContext, nil).Return(resp)
err := Combine(service).Validate(noContext, nil)
if err != resp {
t.Errorf("expected convert service error")
}
}

27
plugin/validator/noop.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build oss
package converter
import (
"context"
"github.com/drone/drone/core"
)
type noop struct{}
func (noop) Validate(context.Context, *core.ConvertArgs) error { return nil }

113
plugin/validator/remote.go Normal file
View File

@ -0,0 +1,113 @@
// 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.
// +build !oss
package validator
import (
"context"
"time"
"github.com/drone/drone-go/drone"
"github.com/drone/drone-go/plugin/validator"
"github.com/drone/drone/core"
)
// Remote returns a conversion service that converts the
// configuration file using a remote http service.
func Remote(endpoint, signer string, skipVerify bool) core.ValidateService {
return &remote{
endpoint: endpoint,
secret: signer,
skipVerify: skipVerify,
}
}
type remote struct {
endpoint string
secret string
skipVerify bool
}
func (g *remote) Validate(ctx context.Context, in *core.ValidateArgs) error {
if g.endpoint == "" {
return nil
}
// include a timeout to prevent an API call from
// hanging the build process indefinitely. The
// external service must return a request within
// one minute.
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
req := &validator.Request{
Repo: toRepo(in.Repo),
Build: toBuild(in.Build),
Config: drone.Config{
Data: in.Config.Data,
},
}
client := validator.Client(g.endpoint, g.secret, g.skipVerify)
return client.Validate(ctx, req)
}
func toRepo(from *core.Repository) drone.Repo {
return drone.Repo{
ID: from.ID,
UID: from.UID,
UserID: from.UserID,
Namespace: from.Namespace,
Name: from.Name,
Slug: from.Slug,
SCM: from.SCM,
HTTPURL: from.HTTPURL,
SSHURL: from.SSHURL,
Link: from.Link,
Branch: from.Branch,
Private: from.Private,
Visibility: from.Visibility,
Active: from.Active,
Config: from.Config,
Trusted: from.Trusted,
Protected: from.Protected,
Timeout: from.Timeout,
}
}
func toBuild(from *core.Build) drone.Build {
return drone.Build{
ID: from.ID,
RepoID: from.RepoID,
Trigger: from.Trigger,
Number: from.Number,
Parent: from.Parent,
Status: from.Status,
Error: from.Error,
Event: from.Event,
Action: from.Action,
Link: from.Link,
Timestamp: from.Timestamp,
Title: from.Title,
Message: from.Message,
Before: from.Before,
After: from.After,
Ref: from.Ref,
Fork: from.Fork,
Source: from.Source,
Target: from.Target,
Author: from.Author,
AuthorName: from.AuthorName,
AuthorEmail: from.AuthorEmail,
AuthorAvatar: from.AuthorAvatar,
Sender: from.Sender,
Params: from.Params,
Deploy: from.Deploy,
Started: from.Started,
Finished: from.Finished,
Created: from.Created,
Updated: from.Updated,
Version: from.Version,
}
}

View File

@ -0,0 +1,27 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build oss
package validator
import (
"github.com/drone/drone/core"
)
// Remote returns a conversion service that converts the
// configuration file using a remote http service.
func Remote(endpoint, signer string, skipVerify bool) core.ValidateService {
return new(noop)
}

View File

@ -0,0 +1,7 @@
// 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.
// +build !oss
package validator

View File

@ -40,6 +40,7 @@ type triggerer struct {
sched core.Scheduler
repos core.RepositoryStore
users core.UserStore
validate core.ValidateService
hooks core.WebhookSender
}
@ -53,6 +54,7 @@ func New(
sched core.Scheduler,
repos core.RepositoryStore,
users core.UserStore,
validate core.ValidateService,
hooks core.WebhookSender,
) core.Triggerer {
return &triggerer{
@ -64,6 +66,7 @@ func New(
sched: sched,
repos: repos,
users: users,
validate: validate,
hooks: hooks,
}
}
@ -189,7 +192,6 @@ func (t *triggerer) Trigger(ctx context.Context, repo *core.Repository, base *co
Repo: repo,
Build: tmpBuild,
}
raw, err := t.config.Find(ctx, req)
if err != nil {
logger = logger.WithError(err)
@ -229,6 +231,18 @@ func (t *triggerer) Trigger(ctx context.Context, repo *core.Repository, base *co
return t.createBuildError(ctx, repo, base, err.Error())
}
err = t.validate.Validate(ctx, &core.ValidateArgs{
User: user,
Repo: repo,
Build: tmpBuild,
Config: raw,
})
if err != nil {
logger = logger.WithError(err)
logger.Warnln("trigger: yaml validation error")
return t.createBuildError(ctx, repo, base, err.Error())
}
err = linter.Manifest(manifest, repo.Trusted)
if err != nil {
logger = logger.WithError(err)

View File

@ -63,6 +63,9 @@ func TestTrigger(t *testing.T) {
mockConvertService := mock.NewMockConvertService(controller)
mockConvertService.EXPECT().Convert(gomock.Any(), gomock.Any()).Return(dummyYaml, nil)
mockValidateService := mock.NewMockValidateService(controller)
mockValidateService.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil)
mockStatus := mock.NewMockStatusService(controller)
mockStatus.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Do(checkStatus)
@ -84,6 +87,7 @@ func TestTrigger(t *testing.T) {
mockQueue,
mockRepos,
mockUsers,
mockValidateService,
mockWebhooks,
)
@ -110,6 +114,7 @@ func TestTrigger_SkipCI(t *testing.T) {
nil,
nil,
nil,
nil,
)
dummyHookSkip := *dummyHook
dummyHookSkip.Message = "foo [CI SKIP] bar"
@ -137,6 +142,7 @@ func TestTrigger_NoOwner(t *testing.T) {
nil,
mockUsers,
nil,
nil,
)
_, err := triggerer.Trigger(noContext, dummyRepo, dummyHook)
@ -167,6 +173,7 @@ func TestTrigger_MissingYaml(t *testing.T) {
nil,
mockUsers,
nil,
nil,
)
_, err := triggerer.Trigger(noContext, dummyRepo, dummyHook)
@ -206,6 +213,7 @@ func TestTrigger_ErrorYaml(t *testing.T) {
mockRepos,
mockUsers,
nil,
nil,
)
build, err := triggerer.Trigger(noContext, dummyRepo, dummyHook)
@ -239,6 +247,9 @@ func TestTrigger_SkipBranch(t *testing.T) {
mockConvertService := mock.NewMockConvertService(controller)
mockConvertService.EXPECT().Convert(gomock.Any(), gomock.Any()).Return(dummyYamlSkipBranch, nil)
mockValidateService := mock.NewMockValidateService(controller)
mockValidateService.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil)
triggerer := New(
mockConfigService,
mockConvertService,
@ -248,6 +259,7 @@ func TestTrigger_SkipBranch(t *testing.T) {
nil,
nil,
mockUsers,
mockValidateService,
nil,
)
@ -272,6 +284,9 @@ func TestTrigger_SkipEvent(t *testing.T) {
mockConvertService := mock.NewMockConvertService(controller)
mockConvertService.EXPECT().Convert(gomock.Any(), gomock.Any()).Return(dummyYamlSkipEvent, nil)
mockValidateService := mock.NewMockValidateService(controller)
mockValidateService.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil)
triggerer := New(
mockConfigService,
mockConvertService,
@ -281,6 +296,7 @@ func TestTrigger_SkipEvent(t *testing.T) {
nil,
nil,
mockUsers,
mockValidateService,
nil,
)
@ -305,6 +321,9 @@ func TestTrigger_SkipAction(t *testing.T) {
mockConvertService := mock.NewMockConvertService(controller)
mockConvertService.EXPECT().Convert(gomock.Any(), gomock.Any()).Return(dummyYamlSkipAction, nil)
mockValidateService := mock.NewMockValidateService(controller)
mockValidateService.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil)
triggerer := New(
mockConfigService,
mockConvertService,
@ -314,6 +333,7 @@ func TestTrigger_SkipAction(t *testing.T) {
nil,
nil,
mockUsers,
mockValidateService,
nil,
)
@ -342,6 +362,9 @@ func TestTrigger_ErrorIncrement(t *testing.T) {
mockConvertService := mock.NewMockConvertService(controller)
mockConvertService.EXPECT().Convert(gomock.Any(), gomock.Any()).Return(dummyYaml, nil)
mockValidateService := mock.NewMockValidateService(controller)
mockValidateService.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil)
triggerer := New(
mockConfigService,
mockConvertService,
@ -351,6 +374,7 @@ func TestTrigger_ErrorIncrement(t *testing.T) {
nil,
mockRepos,
mockUsers,
mockValidateService,
nil,
)