|
|
|
@@ -0,0 +1,160 @@
|
|
|
|
|
package provisioning
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
|
|
|
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
|
|
|
"k8s.io/apiserver/pkg/admission"
|
|
|
|
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
|
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
|
|
|
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
|
|
|
|
"k8s.io/kube-openapi/pkg/common"
|
|
|
|
|
"k8s.io/kube-openapi/pkg/spec3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
_ builder.APIGroupBuilder = (*APIBuilder)(nil)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type APIBuilder struct{}
|
|
|
|
|
|
|
|
|
|
// NewAPIBuilder creates an API builder.
|
|
|
|
|
// It avoids anything that is core to Grafana, such that it can be used in a multi-tenant service down the line.
|
|
|
|
|
// This means there are no hidden dependencies, and no use of e.g. *settings.Cfg.
|
|
|
|
|
func NewAPIBuilder() *APIBuilder {
|
|
|
|
|
return &APIBuilder{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RegisterAPIService returns an API builder, from [NewAPIBuilder]. It is called by Wire.
|
|
|
|
|
// This function happily uses services core to Grafana, and does not need to be multi-tenancy-compatible.
|
|
|
|
|
func RegisterAPIService(
|
|
|
|
|
features featuremgmt.FeatureToggles,
|
|
|
|
|
apiregistration builder.APIRegistrar,
|
|
|
|
|
) (*APIBuilder, error) {
|
|
|
|
|
if !features.IsEnabledGlobally(featuremgmt.FlagProvisioning) &&
|
|
|
|
|
!features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
|
|
|
|
|
return nil, nil // skip registration unless opting into experimental apis OR the feature specifically
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder := NewAPIBuilder()
|
|
|
|
|
apiregistration.RegisterAPI(builder)
|
|
|
|
|
return builder, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *APIBuilder) GetAuthorizer() authorizer.Authorizer {
|
|
|
|
|
return authorizer.AuthorizerFunc(
|
|
|
|
|
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
|
|
|
|
// TODO: Implement a webhook authoriser somehow.
|
|
|
|
|
|
|
|
|
|
// fallback to the standard authorizer
|
|
|
|
|
return authorizer.DecisionNoOpinion, "", nil
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *APIBuilder) GetGroupVersion() schema.GroupVersion {
|
|
|
|
|
return provisioning.SchemeGroupVersion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *APIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
|
|
|
|
err := provisioning.AddToScheme(scheme)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This is required for --server-side apply
|
|
|
|
|
err = provisioning.AddKnownTypes(provisioning.InternalGroupVersion, scheme)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
metav1.AddToGroupVersion(scheme, provisioning.SchemeGroupVersion)
|
|
|
|
|
// Only 1 version (for now?)
|
|
|
|
|
return scheme.SetVersionPriority(provisioning.SchemeGroupVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *APIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error {
|
|
|
|
|
repositoryStorage, err := grafanaregistry.NewRegistryStore(opts.Scheme, provisioning.RepositoryResourceInfo, opts.OptsGetter)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to create repository storage: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repositoryStatusStorage := grafanaregistry.NewRegistryStatusStore(opts.Scheme, repositoryStorage)
|
|
|
|
|
|
|
|
|
|
storage := map[string]rest.Storage{}
|
|
|
|
|
storage[provisioning.RepositoryResourceInfo.StoragePath()] = repositoryStorage
|
|
|
|
|
storage[provisioning.RepositoryResourceInfo.StoragePath("status")] = repositoryStatusStorage
|
|
|
|
|
apiGroupInfo.VersionedResourcesStorageMap[provisioning.VERSION] = storage
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *APIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
|
|
|
|
obj := a.GetObject()
|
|
|
|
|
|
|
|
|
|
if obj == nil || a.GetOperation() == admission.Connect {
|
|
|
|
|
return nil // This is normal for sub-resource
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r, ok := obj.(*provisioning.Repository)
|
|
|
|
|
if !ok {
|
|
|
|
|
return fmt.Errorf("expected repository configuration")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Do something based on the resource we got.
|
|
|
|
|
_ = r
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *APIBuilder) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
|
|
|
|
|
obj := a.GetObject()
|
|
|
|
|
if obj == nil || a.GetOperation() == admission.Connect {
|
|
|
|
|
return nil // This is normal for sub-resource
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var list field.ErrorList
|
|
|
|
|
// TODO: Fill the list with validation errors.
|
|
|
|
|
|
|
|
|
|
if len(list) > 0 {
|
|
|
|
|
return apierrors.NewInvalid(
|
|
|
|
|
provisioning.RepositoryResourceInfo.GroupVersionKind().GroupKind(),
|
|
|
|
|
a.GetName(), list)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *APIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
|
|
|
|
|
return provisioning.GetOpenAPIDefinitions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartHookFunc, error) {
|
|
|
|
|
postStartHooks := map[string]genericapiserver.PostStartHookFunc{
|
|
|
|
|
"grafana-provisioning": func(postStartHookCtx genericapiserver.PostStartHookContext) error {
|
|
|
|
|
// TODO: Set up a shared informer for a controller and a watcher with workers.
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return postStartHooks, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *APIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
|
|
|
|
oas.Info.Description = "Provisioning"
|
|
|
|
|
|
|
|
|
|
root := "/apis/" + b.GetGroupVersion().String() + "/"
|
|
|
|
|
|
|
|
|
|
// The root API discovery list
|
|
|
|
|
sub := oas.Paths.Paths[root]
|
|
|
|
|
if sub != nil && sub.Get != nil {
|
|
|
|
|
sub.Get.Tags = []string{"API Discovery"} // sorts first in the list
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return oas, nil
|
|
|
|
|
}
|