Provisioning: Build and use repository factory in repository controller (#110585)

Build and use repository factory
This commit is contained in:
Roberto Jiménez Sánchez
2025-09-04 13:12:56 +02:00
committed by GitHub
parent 145a50be4d
commit 3d009ff7ed
3 changed files with 120 additions and 8 deletions
+25 -8
View File
@@ -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
+94
View File
@@ -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
}
@@ -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)