Files
grafana/pkg/registry/apis/provisioning/webhooks/pullrequest/worker.go
T
Roberto Jiménez Sánchez 1cc21a0705 Provisioning: Make image renderer note optional in PR comments (#113837)
* Provisioning: Remove image renderer note from PR comment template

Removes the 'NOTE: The image renderer is not configured' message from
the pull request comment template when image renderer is unavailable.
This addresses issue #656 in git-ui-sync-project.

- Updated commentTemplateMissingImageRenderer to be empty
- Updated testdata to reflect the change
- All unit tests pass

* Provisioning: Make image renderer note optional in PR comments

Make the image renderer note in pull request comments optional based on
the allowImageRendering configuration flag. When enabled, the note now
includes a link to the setup documentation.

- Add showImageRendererNote boolean field to commenter struct
- Update NewCommenter to accept showImageRendererNote parameter
- Update template to conditionally show note with documentation link
- Pass allowImageRendering from APIBuilder to commenter in register.go
- Update ProvidePullRequestWorker to use cfg.ProvisioningAllowImageRendering
- Add tests to verify note appears/disappears based on flag

Fixes https://github.com/grafana/git-ui-sync-project/issues/656

* Format code with go fmt

* Remove redundant text from image renderer note

Remove 'The image renderer is not configured.' from the note message.
The note now focuses on actionable guidance with the documentation link.

* Fix compilation error: use cfg.ProvisioningAllowImageRendering directly

Cannot access unexported field allowImageRendering from webhooks package.
Use cfg.ProvisioningAllowImageRendering directly since we have access to cfg.
2025-11-14 10:33:28 +01:00

172 lines
5.8 KiB
Go

package pullrequest
import (
"context"
"errors"
"fmt"
"time"
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/registry/apis/provisioning/utils"
"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"
"github.com/prometheus/client_golang/prometheus"
)
func ProvidePullRequestWorker(
cfg *setting.Cfg,
renderer rendering.Service,
blobstore resource.ResourceClient,
configProvider apiserver.RestConfigProvider,
registry prometheus.Registerer,
) *PullRequestWorker {
urlProvider := func(_ context.Context, _ 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, registry)
commenter := NewCommenter(cfg.ProvisioningAllowImageRendering)
return NewPullRequestWorker(evaluator, commenter, registry)
}
//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
metrics pullRequestMetrics
}
func NewPullRequestWorker(evaluator Evaluator, commenter Commenter, registry prometheus.Registerer) *PullRequestWorker {
metrics := registerPullRequestMetrics(registry)
return &PullRequestWorker{
evaluator: evaluator,
commenter: commenter,
metrics: metrics,
}
}
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
startTime := time.Now()
outcome := utils.ErrorOutcome
defer func() {
duration := time.Since(startTime)
c.metrics.recordProcessed(outcome, duration)
}()
if opts == nil {
return apierrors.NewBadRequest("missing spec.pr")
}
logger := logging.FromContext(ctx).With("pr", opts.PR, "repo", repo.Config().GetName(), "namespace", job.GetNamespace())
if opts.Ref == "" {
logger.Debug("missing spec.ref")
return apierrors.NewBadRequest("missing spec.ref")
}
// FIXME: this is leaky because it's supposed to be already a PullRequestRepo
if cfg.GitHub == nil {
logger.Debug("expecting github configuration")
return apierrors.NewBadRequest("expecting github configuration")
}
reader, ok := repo.(repository.Reader)
if !ok {
logger.Debug("pull request job submitted targeting repository that is not a Reader")
return errors.New("pull request job submitted targeting repository that is not a Reader")
}
prRepo, ok := repo.(PullRequestRepo)
if !ok {
logger.Debug("pull request job submitted targeting repository that is not a PullRequestRepo")
return fmt.Errorf("repository is not a pull request repository")
}
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 {
logger.Error("failed to list pull request files", "error", err)
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 {
logger.Error("failed to calculate changes", "error", err)
return fmt.Errorf("calculate changes: %w", err)
}
if err := c.commenter.Comment(ctx, prRepo, opts.PR, changeInfo); err != nil {
c.metrics.recordCommentPosted(utils.ErrorOutcome)
return fmt.Errorf("comment pull request: %w", err)
}
outcome = utils.SuccessOutcome
c.metrics.recordCommentPosted(utils.SuccessOutcome)
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
}