* Provisioning: Cleanup tester interface * undo accidental change * cleanup * cleanup test
200 lines
5.4 KiB
Go
200 lines
5.4 KiB
Go
package provisioning
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"time"
|
|
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apiserver/pkg/endpoints/request"
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
|
|
|
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
|
appcontroller "github.com/grafana/grafana/apps/provisioning/pkg/controller"
|
|
"github.com/grafana/grafana/apps/provisioning/pkg/repository"
|
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/controller"
|
|
)
|
|
|
|
type StatusPatcherProvider interface {
|
|
GetStatusPatcher() *appcontroller.RepositoryStatusPatcher
|
|
}
|
|
|
|
type HealthCheckerProvider interface {
|
|
GetHealthChecker() *controller.HealthChecker
|
|
}
|
|
|
|
type testConnector struct {
|
|
getter RepoGetter
|
|
factory repository.Factory
|
|
healthProvider HealthCheckerProvider
|
|
}
|
|
|
|
func NewTestConnector(
|
|
getter RepoGetter,
|
|
factory repository.Factory,
|
|
healthProvider HealthCheckerProvider,
|
|
) *testConnector {
|
|
return &testConnector{
|
|
factory: factory,
|
|
getter: getter,
|
|
healthProvider: healthProvider,
|
|
}
|
|
}
|
|
|
|
func (*testConnector) New() runtime.Object {
|
|
return &provisioning.TestResults{}
|
|
}
|
|
|
|
func (*testConnector) Destroy() {}
|
|
|
|
func (*testConnector) ProducesMIMETypes(verb string) []string {
|
|
return []string{"application/json"}
|
|
}
|
|
|
|
func (*testConnector) ProducesObject(verb string) any {
|
|
return &provisioning.TestResults{}
|
|
}
|
|
|
|
func (*testConnector) ConnectMethods() []string {
|
|
return []string{http.MethodPost}
|
|
}
|
|
|
|
func (*testConnector) NewConnectOptions() (runtime.Object, bool, string) {
|
|
return nil, false, ""
|
|
}
|
|
|
|
func (s *testConnector) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
|
|
ns, ok := request.NamespaceFrom(ctx)
|
|
if !ok {
|
|
return nil, fmt.Errorf("missing namespace")
|
|
}
|
|
|
|
return WithTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, err := readBody(r, defaultMaxBodySize)
|
|
if err != nil {
|
|
responder.Error(err)
|
|
return
|
|
}
|
|
var repo repository.Repository
|
|
if len(body) > 0 {
|
|
var cfg provisioning.Repository
|
|
err = json.Unmarshal(body, &cfg)
|
|
if err != nil {
|
|
responder.Error(err)
|
|
return
|
|
}
|
|
|
|
// In case the body is an empty object
|
|
if !reflect.ValueOf(cfg).IsZero() {
|
|
// HACK: Set the name and namespace if not set so that the temporary repository can be created
|
|
// This can be removed once we deprecate legacy secrets is deprecated or we use InLineSecureValues as we
|
|
// use the same field and repository name to detect which one to use.
|
|
if name == "new" {
|
|
// HACK: frontend is passing a "new" we need to remove the hack there as well
|
|
// Otherwise creation will fail as `new` is a reserved word. Not relevant here as we only "test"
|
|
name = "hack-on-hack-for-new"
|
|
} else {
|
|
// Copy previous secure values if they exist
|
|
old, _ := s.getter.GetRepository(ctx, name)
|
|
if old != nil && !old.Config().Secure.IsZero() {
|
|
secure := old.Config().Secure
|
|
if cfg.Secure.Token.IsZero() {
|
|
cfg.Secure.Token = secure.Token
|
|
}
|
|
if cfg.Secure.WebhookSecret.IsZero() {
|
|
cfg.Secure.WebhookSecret = secure.WebhookSecret
|
|
}
|
|
}
|
|
}
|
|
|
|
cfg.SetName(name)
|
|
if cfg.GetNamespace() == "" {
|
|
cfg.SetNamespace(ns)
|
|
}
|
|
|
|
// Create a temporary repository
|
|
tmp, err := s.factory.Build(ctx, &cfg)
|
|
if err != nil {
|
|
responder.Error(err)
|
|
return
|
|
}
|
|
repo = tmp
|
|
}
|
|
}
|
|
|
|
var rsp *provisioning.TestResults
|
|
if repo == nil {
|
|
healthChecker := s.healthProvider.GetHealthChecker()
|
|
if healthChecker == nil {
|
|
// Use precondition failed for when health checker is not ready yet
|
|
responder.Error(&errors.StatusError{
|
|
ErrStatus: metav1.Status{
|
|
Status: metav1.StatusFailure,
|
|
Code: http.StatusPreconditionFailed,
|
|
Reason: metav1.StatusReason("PreconditionFailed"),
|
|
Message: "health checker not initialized yet, please try again",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Testing existing repository - get it and update health
|
|
repo, err = s.getter.GetRepository(ctx, name)
|
|
if err != nil {
|
|
responder.Error(err)
|
|
return
|
|
}
|
|
|
|
// If the last error was not a health check error or empty, return precondition failed
|
|
health := repo.Config().Status.Health
|
|
if health.Error != provisioning.HealthFailureHealth && health.Error != "" {
|
|
rsp = &provisioning.TestResults{
|
|
Success: false,
|
|
Code: http.StatusPreconditionFailed,
|
|
Errors: func() []provisioning.ErrorDetails {
|
|
var errs []provisioning.ErrorDetails
|
|
for _, msg := range health.Message {
|
|
errs = append(errs, provisioning.ErrorDetails{Detail: msg})
|
|
}
|
|
return errs
|
|
}(),
|
|
}
|
|
|
|
if err := healthChecker.RefreshTimestamp(ctx, repo.Config()); err != nil {
|
|
responder.Error(err)
|
|
return
|
|
}
|
|
|
|
responder.Object(rsp.Code, rsp)
|
|
return
|
|
}
|
|
|
|
rsp, _, err = healthChecker.RefreshHealth(ctx, repo)
|
|
if err != nil {
|
|
responder.Error(err)
|
|
return
|
|
}
|
|
} else {
|
|
// Testing temporary repository - just run test without status update
|
|
rsp, err = repository.TestRepository(ctx, repo)
|
|
if err != nil {
|
|
responder.Error(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
responder.Object(rsp.Code, rsp)
|
|
}), 30*time.Second), nil
|
|
}
|
|
|
|
var (
|
|
_ rest.Storage = (*testConnector)(nil)
|
|
_ rest.Connecter = (*testConnector)(nil)
|
|
_ rest.StorageMetadata = (*testConnector)(nil)
|
|
)
|