mirror of https://github.com/grafana/grafana.git
148 lines
4.7 KiB
Go
148 lines
4.7 KiB
Go
package pullrequest
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
"github.com/grafana/grafana-app-sdk/logging"
|
|
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
|
"github.com/grafana/grafana/apps/provisioning/pkg/repository"
|
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
|
|
"github.com/grafana/grafana/pkg/services/apiserver"
|
|
"github.com/grafana/grafana/pkg/services/rendering"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
|
)
|
|
|
|
func ProvidePullRequestWorker(
|
|
cfg *setting.Cfg,
|
|
renderer rendering.Service,
|
|
blobstore resource.ResourceClient,
|
|
configProvider apiserver.RestConfigProvider,
|
|
) *PullRequestWorker {
|
|
urlProvider := func(_ string) string {
|
|
return cfg.AppURL
|
|
}
|
|
|
|
// FIXME: we should create providers for client and parsers, so that we don't have
|
|
// multiple connections for webhooks
|
|
clients := resources.NewClientFactory(configProvider)
|
|
parsers := resources.NewParserFactory(clients)
|
|
screenshotRenderer := NewScreenshotRenderer(renderer, blobstore)
|
|
evaluator := NewEvaluator(screenshotRenderer, parsers, urlProvider)
|
|
commenter := NewCommenter()
|
|
|
|
return NewPullRequestWorker(evaluator, commenter)
|
|
}
|
|
|
|
//go:generate mockery --name=PullRequestRepo --structname=MockPullRequestRepo --inpackage --filename=mock_pullrequest_repo.go --with-expecter
|
|
type PullRequestRepo interface {
|
|
Config() *provisioning.Repository
|
|
Read(ctx context.Context, path, ref string) (*repository.FileInfo, error)
|
|
CompareFiles(ctx context.Context, base, ref string) ([]repository.VersionedFileChange, error)
|
|
CommentPullRequest(ctx context.Context, pr int, comment string) error
|
|
}
|
|
|
|
//go:generate mockery --name=Evaluator --structname=MockEvaluator --inpackage --filename=mock_evaluator.go --with-expecter
|
|
type Evaluator interface {
|
|
Evaluate(ctx context.Context, repo repository.Reader, opts provisioning.PullRequestJobOptions, changes []repository.VersionedFileChange, progress jobs.JobProgressRecorder) (changeInfo, error)
|
|
}
|
|
|
|
//go:generate mockery --name=Commenter --structname=MockCommenter --inpackage --filename=mock_commenter.go --with-expecter
|
|
type Commenter interface {
|
|
Comment(ctx context.Context, repo PullRequestRepo, pr int, changeInfo changeInfo) error
|
|
}
|
|
|
|
type PullRequestWorker struct {
|
|
evaluator Evaluator
|
|
commenter Commenter
|
|
}
|
|
|
|
func NewPullRequestWorker(evaluator Evaluator, commenter Commenter) *PullRequestWorker {
|
|
return &PullRequestWorker{
|
|
evaluator: evaluator,
|
|
commenter: commenter,
|
|
}
|
|
}
|
|
|
|
func (c *PullRequestWorker) IsSupported(ctx context.Context, job provisioning.Job) bool {
|
|
return job.Spec.Action == provisioning.JobActionPullRequest
|
|
}
|
|
|
|
func (c *PullRequestWorker) Process(ctx context.Context,
|
|
repo repository.Repository,
|
|
job provisioning.Job,
|
|
progress jobs.JobProgressRecorder,
|
|
) error {
|
|
cfg := repo.Config().Spec
|
|
opts := job.Spec.PullRequest
|
|
if opts == nil {
|
|
return apierrors.NewBadRequest("missing spec.pr")
|
|
}
|
|
|
|
if opts.Ref == "" {
|
|
return apierrors.NewBadRequest("missing spec.ref")
|
|
}
|
|
|
|
// FIXME: this is leaky because it's supposed to be already a PullRequestRepo
|
|
if cfg.GitHub == nil {
|
|
return apierrors.NewBadRequest("expecting github configuration")
|
|
}
|
|
|
|
reader, ok := repo.(repository.Reader)
|
|
if !ok {
|
|
return errors.New("pull request job submitted targeting repository that is not a Reader")
|
|
}
|
|
|
|
prRepo, ok := repo.(PullRequestRepo)
|
|
if !ok {
|
|
return fmt.Errorf("repository is not a pull request repository")
|
|
}
|
|
|
|
logger := logging.FromContext(ctx).With("pr", opts.PR)
|
|
logger.Info("process pull request")
|
|
defer logger.Info("pull request processed")
|
|
|
|
progress.SetMessage(ctx, "listing pull request files")
|
|
// FIXME: this is leaky because it's supposed to be already a PullRequestRepo
|
|
base := cfg.GitHub.Branch
|
|
files, err := prRepo.CompareFiles(ctx, base, opts.Ref)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list pull request files: %w", err)
|
|
}
|
|
|
|
files = onlySupportedFiles(files)
|
|
if len(files) == 0 {
|
|
progress.SetFinalMessage(ctx, "no files to process")
|
|
return nil
|
|
}
|
|
|
|
changeInfo, err := c.evaluator.Evaluate(ctx, reader, *opts, files, progress)
|
|
if err != nil {
|
|
return fmt.Errorf("calculate changes: %w", err)
|
|
}
|
|
|
|
if err := c.commenter.Comment(ctx, prRepo, opts.PR, changeInfo); err != nil {
|
|
return fmt.Errorf("comment pull request: %w", err)
|
|
}
|
|
logger.Info("preview comment added")
|
|
|
|
return nil
|
|
}
|
|
|
|
// Remove files we should not try to process
|
|
func onlySupportedFiles(files []repository.VersionedFileChange) (ret []repository.VersionedFileChange) {
|
|
for _, file := range files {
|
|
if file.Action == repository.FileActionIgnored || resources.IsPathSupported(file.Path) != nil {
|
|
continue
|
|
}
|
|
ret = append(ret, file)
|
|
}
|
|
|
|
return
|
|
}
|