Provisioning: add webhook support in API Server and Operator (#110673)

* Provisioning: add webhook support in API Server

* updating Extra interface

* adding extra with workers interface

* reverting extraWithWorkers in RegisterAPIService

* adding extra job worker provider

* adding new extra job provider

* Wire things differently

* Remove unused GetJobs

* Pass url variable as string

* Support webhooks in controller

* Fix condition

* Change the naming

---------

Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com>
This commit is contained in:
Daniele Stefano Ferru
2025-09-08 19:39:05 +02:00
committed by GitHub
co-authored by Roberto Jimenez Sanchez
parent 32e997d282
commit 76976ef648
10 changed files with 144 additions and 38 deletions
@@ -106,3 +106,19 @@ func (r *screenshotRenderer) RenderScreenshot(ctx context.Context, repo provisio
return fmt.Sprintf("apis/%s/namespaces/%s/repositories/%s/render/%s",
provisioning.APIVERSION, repo.Namespace, repo.Name, rsp.Uid), nil
}
type NoOpRenderer struct{}
func NewNoOpRenderer() ScreenshotRenderer {
return &NoOpRenderer{}
}
func (r *NoOpRenderer) IsAvailable(_ context.Context) bool {
return false
}
func (r *NoOpRenderer) RenderScreenshot(
_ context.Context, _ provisioning.ResourceRepositoryInfo, _ string, _ url.Values,
) (string, error) {
return "", nil
}
@@ -12,8 +12,33 @@ import (
"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
@@ -28,6 +28,7 @@ type WebhookExtraBuilder struct {
urlProvider func(namespace string) string
}
// FIXME: separate the URL provider from connector to simplify operators
func (b *WebhookExtraBuilder) WebhookURL(ctx context.Context, r *provisioning.Repository) string {
if !b.isPublic {
return ""
@@ -57,7 +58,7 @@ func isPublicURL(url string) bool {
!strings.HasPrefix(url, "https://172.16.")
}
func ProvideWebhooks(
func ProvideWebhooksWithImages(
cfg *setting.Cfg,
renderer rendering.Service,
blobstore resource.ResourceClient,
@@ -87,7 +88,7 @@ func ProvideWebhooks(
commenter := pullrequest.NewCommenter()
pullRequestWorker := pullrequest.NewPullRequestWorker(evaluator, commenter)
return NewWebhookExtra(
return NewWebhookExtraWithImages(
render,
webhook,
urlProvider,
@@ -97,21 +98,40 @@ func ProvideWebhooks(
}
}
// WebhookExtra implements the Extra interface for webhooks
func ProvideWebhooks(provisioningURL string) *WebhookExtraBuilder {
urlProvider := func(_ string) string {
return provisioningURL
}
isPublic := isPublicURL(urlProvider(""))
return &WebhookExtraBuilder{
isPublic: isPublic,
urlProvider: urlProvider,
ExtraBuilder: func(b *provisioningapis.APIBuilder) provisioningapis.Extra {
screenshotRenderer := pullrequest.NewNoOpRenderer()
webhook := NewWebhookConnector(isPublic, b, screenshotRenderer)
return NewWebhookExtra(webhook)
},
}
}
// WebhookExtraWithImages implements the Extra interface for webhooks
// to wrap around
type WebhookExtra struct {
type WebhookExtraWithImages struct {
render *renderConnector
webhook *webhookConnector
workers []jobs.Worker
}
func NewWebhookExtra(
func NewWebhookExtraWithImages(
render *renderConnector,
webhook *webhookConnector,
urlProvider func(namespace string) string,
workers []jobs.Worker,
) *WebhookExtra {
return &WebhookExtra{
) *WebhookExtraWithImages {
return &WebhookExtraWithImages{
render: render,
webhook: webhook,
workers: workers,
@@ -119,7 +139,7 @@ func NewWebhookExtra(
}
// Authorize delegates authorization to the webhook connector
func (e *WebhookExtra) Authorize(ctx context.Context, a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
func (e *WebhookExtraWithImages) Authorize(ctx context.Context, a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
webhookDecision, webhookReason, webhookErr := e.webhook.Authorize(ctx, a)
if webhookDecision != authorizer.DecisionNoOpinion {
return webhookDecision, webhookReason, webhookErr
@@ -129,7 +149,7 @@ func (e *WebhookExtra) Authorize(ctx context.Context, a authorizer.Attributes) (
}
// UpdateStorage updates the storage with both render and webhook connectors
func (e *WebhookExtra) UpdateStorage(storage map[string]rest.Storage) error {
func (e *WebhookExtraWithImages) UpdateStorage(storage map[string]rest.Storage) error {
if err := e.webhook.UpdateStorage(storage); err != nil {
return err
}
@@ -138,7 +158,7 @@ func (e *WebhookExtra) UpdateStorage(storage map[string]rest.Storage) error {
}
// PostProcessOpenAPI processes OpenAPI specs for both connectors
func (e *WebhookExtra) PostProcessOpenAPI(oas *spec3.OpenAPI) error {
func (e *WebhookExtraWithImages) PostProcessOpenAPI(oas *spec3.OpenAPI) error {
if err := e.webhook.PostProcessOpenAPI(oas); err != nil {
return err
}
@@ -146,7 +166,25 @@ func (e *WebhookExtra) PostProcessOpenAPI(oas *spec3.OpenAPI) error {
return e.render.PostProcessOpenAPI(oas)
}
// GetJobWorkers returns job workers from the webhook connector
func (e *WebhookExtra) GetJobWorkers() []jobs.Worker {
return e.workers
type WebhookExtra struct {
webhook *webhookConnector
}
func NewWebhookExtra(webhook *webhookConnector) *WebhookExtra {
return &WebhookExtra{webhook: webhook}
}
// Authorize delegates authorization to the webhook connector
func (e *WebhookExtra) Authorize(ctx context.Context, a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
return e.webhook.Authorize(ctx, a)
}
// UpdateStorage updates the storage with webhook connector
func (e *WebhookExtra) UpdateStorage(storage map[string]rest.Storage) error {
return e.webhook.UpdateStorage(storage)
}
// PostProcessOpenAPI processes OpenAPI specs for webhook connectors
func (e *WebhookExtra) PostProcessOpenAPI(oas *spec3.OpenAPI) error {
return e.webhook.PostProcessOpenAPI(oas)
}