From 3d009ff7edf6095f79e95bc60b728acf04149665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Thu, 4 Sep 2025 13:12:56 +0200 Subject: [PATCH] Provisioning: Build and use repository factory in repository controller (#110585) Build and use repository factory --- apps/provisioning/pkg/controller/repo.go | 33 ++++++-- pkg/operators/provisioning/config.go | 94 +++++++++++++++++++++ pkg/operators/provisioning/repo_operator.go | 1 + 3 files changed, 120 insertions(+), 8 deletions(-) diff --git a/apps/provisioning/pkg/controller/repo.go b/apps/provisioning/pkg/controller/repo.go index 4bec1f36f30..7609d9c5c1a 100644 --- a/apps/provisioning/pkg/controller/repo.go +++ b/apps/provisioning/pkg/controller/repo.go @@ -16,24 +16,28 @@ import ( typedclient "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1" informerv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1" listers "github.com/grafana/grafana/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1" + "github.com/grafana/grafana/apps/provisioning/pkg/repository" ) type RepositoryController struct { - client typedclient.ProvisioningV0alpha1Interface - repoLister listers.RepositoryLister - repoSynced cache.InformerSynced - logger logging.Logger - queue workqueue.TypedRateLimitingInterface[string] + client typedclient.ProvisioningV0alpha1Interface + repoLister listers.RepositoryLister + repoSynced cache.InformerSynced + logger logging.Logger + queue workqueue.TypedRateLimitingInterface[string] + repoFactory repository.Factory } func NewRepositoryController( provisioningClient typedclient.ProvisioningV0alpha1Interface, repoInformer informerv0alpha1.RepositoryInformer, + repoFactory repository.Factory, ) (*RepositoryController, error) { controller := &RepositoryController{ - client: provisioningClient, - repoLister: repoInformer.Lister(), - repoSynced: repoInformer.Informer().HasSynced, + repoFactory: repoFactory, + client: provisioningClient, + repoLister: repoInformer.Lister(), + repoSynced: repoInformer.Informer().HasSynced, logger: logging.NewSLogLogger(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelDebug, })), @@ -139,6 +143,19 @@ func (c *RepositoryController) processRepository(ctx context.Context, key string "generation", repo.Generation, "observedGeneration", repo.Status.ObservedGeneration) + // These lines are here only for testing purposes until we use the real controller + built, err := c.repoFactory.Build(ctx, repo) + if err != nil { + c.logger.Error("Failed to build repository instance", "error", err, "namespace", repo.Namespace, "name", repo.Name) + } else { + results, err := built.Test(ctx) + if err != nil { + c.logger.Error("Repository test failed", "error", err, "namespace", repo.Namespace, "name", repo.Name) + } else { + c.logger.Debug("Repository test results", "results", results, "namespace", repo.Namespace, "name", repo.Name) + } + } + if repo.Generation != repo.Status.ObservedGeneration { repo.Status.ObservedGeneration = repo.Generation diff --git a/pkg/operators/provisioning/config.go b/pkg/operators/provisioning/config.go index 7c3758577f5..f0a19d59df4 100644 --- a/pkg/operators/provisioning/config.go +++ b/pkg/operators/provisioning/config.go @@ -1,6 +1,7 @@ package provisioning import ( + "context" "crypto/x509" "fmt" "net/http" @@ -13,14 +14,20 @@ import ( "github.com/grafana/grafana/pkg/setting" + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" authrt "github.com/grafana/grafana/apps/provisioning/pkg/auth" client "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned" + "github.com/grafana/grafana/apps/provisioning/pkg/repository" + "github.com/grafana/grafana/apps/provisioning/pkg/repository/github" + "github.com/grafana/grafana/apps/provisioning/pkg/repository/local" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" ) // provisioningControllerConfig contains the configuration that overlaps for the jobs and repo controllers type provisioningControllerConfig struct { provisioningClient *client.Clientset resyncInterval time.Duration + repoFactory repository.Factory } // expects: @@ -34,6 +41,9 @@ type provisioningControllerConfig struct { // tls_key_file = // tls_ca_file = // resync_interval = +// repository_types = +// home_path = +// local_permitted_prefixes = func setupFromConfig(cfg *setting.Cfg) (controllerCfg *provisioningControllerConfig, err error) { if cfg == nil { return nil, fmt.Errorf("no configuration available") @@ -86,8 +96,15 @@ func setupFromConfig(cfg *setting.Cfg) (controllerCfg *provisioningControllerCon return nil, fmt.Errorf("failed to create provisioning client: %w", err) } + // TODO: Replace with a real decrypter that uses the Grafana secret service + repoFactory, err := setupRepoFactory(cfg, emptyValuesDecrypter, provisioningClient) + if err != nil { + return nil, fmt.Errorf("failed to setup repository getter: %w", err) + } + return &provisioningControllerConfig{ provisioningClient: provisioningClient, + repoFactory: repoFactory, resyncInterval: operatorSec.Key("resync_interval").MustDuration(60 * time.Second), }, nil } @@ -120,3 +137,80 @@ func buildTLSConfig(insecure bool, certFile, keyFile, caFile string) (rest.TLSCl return tlsConfig, nil } + +// emptyValuesDecrypter is a decrypter that always returns empty values. +// This is a temporary implementation and should be replaced with a real decrypter. +// TODO: remove this and use a real decrypter that uses the Grafana secret service +func emptyValuesDecrypter(r *provisioning.Repository) repository.SecureValues { + return &emptyValues{} +} + +// emptyValues is a that always returns empty values. +// This is a temporary implementation and should be replaced with a real decrypter. +// TODO: remove this and use a real decrypter that uses the Grafana secret service +type emptyValues struct{} + +func (s *emptyValues) Token(ctx context.Context) (common.RawSecureValue, error) { + return common.NewSecretValue(""), nil +} + +func (s *emptyValues) WebhookSecret(ctx context.Context) (common.RawSecureValue, error) { + return common.NewSecretValue(""), nil +} + +func setupRepoFactory( + cfg *setting.Cfg, + decrypter repository.Decrypter, + provisioningClient *client.Clientset, +) (repository.Factory, error) { + operatorSec := cfg.SectionWithEnvOverrides("operator") + repoTypes := operatorSec.Key("repository_types").Strings("|") + + // TODO: This depends on the different flavor of Grafana + // https://github.com/grafana/git-ui-sync-project/issues/495 + extras := make([]repository.Extra, 0) + alreadyRegistered := make(map[provisioning.RepositoryType]struct{}) + + for _, t := range repoTypes { + if _, ok := alreadyRegistered[provisioning.RepositoryType(t)]; ok { + continue + } + alreadyRegistered[provisioning.RepositoryType(t)] = struct{}{} + + switch provisioning.RepositoryType(t) { + case provisioning.GitHubRepositoryType: + extras = append(extras, github.Extra( + decrypter, + github.ProvideFactory(), + // TODO: we need to plug the webhook builder here for webhooks to be created in repository controller + // https://github.com/grafana/git-ui-sync-project/issues/455 + nil, + ), + ) + case provisioning.LocalRepositoryType: + homePath := operatorSec.Key("home_path").String() + if homePath == "" { + return nil, fmt.Errorf("home_path is required in [operator] section for local repository type") + } + + permittedPrefixes := operatorSec.Key("local_permitted_prefixes").Strings("|") + if len(permittedPrefixes) == 0 { + return nil, fmt.Errorf("local_permitted_prefixes is required in [operator] section for local repository type") + } + + extras = append(extras, local.Extra( + homePath, + permittedPrefixes, + )) + default: + return nil, fmt.Errorf("unsupported repository type: %s", t) + } + } + + repoFactory, err := repository.ProvideFactory(extras) + if err != nil { + return nil, fmt.Errorf("create repository factory: %w", err) + } + + return repoFactory, nil +} diff --git a/pkg/operators/provisioning/repo_operator.go b/pkg/operators/provisioning/repo_operator.go index 0d3cbe02c37..4889deed55d 100644 --- a/pkg/operators/provisioning/repo_operator.go +++ b/pkg/operators/provisioning/repo_operator.go @@ -50,6 +50,7 @@ func RunRepoController(opts standalone.BuildInfo, c *cli.Context, cfg *setting.C controller, err := controller.NewRepositoryController( controllerCfg.provisioningClient.ProvisioningV0alpha1(), repoInformer, + controllerCfg.repoFactory, ) if err != nil { return fmt.Errorf("failed to create repository controller: %w", err)