|
|
|
@@ -6,6 +6,7 @@ import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"maps"
|
|
|
|
|
|
|
|
|
|
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
|
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
@@ -20,7 +21,6 @@ import (
|
|
|
|
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
|
|
|
|
|
|
|
|
|
claims "github.com/grafana/authlib/types"
|
|
|
|
|
|
|
|
|
|
internal "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard"
|
|
|
|
|
dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
|
|
|
|
|
dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
|
|
|
@@ -37,9 +37,11 @@ import (
|
|
|
|
|
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
|
|
|
|
|
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
|
|
|
authsvc "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
|
|
|
dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/folder"
|
|
|
|
@@ -77,6 +79,7 @@ type DashboardsAPIBuilder struct {
|
|
|
|
|
dashboardService dashboards.DashboardService
|
|
|
|
|
features featuremgmt.FeatureToggles
|
|
|
|
|
|
|
|
|
|
authorizer authorizer.Authorizer
|
|
|
|
|
accessControl accesscontrol.AccessControl
|
|
|
|
|
accessClient claims.AccessClient
|
|
|
|
|
legacy *DashboardStorage
|
|
|
|
@@ -92,10 +95,14 @@ type DashboardsAPIBuilder struct {
|
|
|
|
|
ProvisioningService provisioning.ProvisioningService
|
|
|
|
|
cfg *setting.Cfg
|
|
|
|
|
dualWriter dualwrite.Service
|
|
|
|
|
folderClient client.K8sHandler
|
|
|
|
|
|
|
|
|
|
log log.Logger
|
|
|
|
|
reg prometheus.Registerer
|
|
|
|
|
// only one of the two is required to be set, folderClientProvider is used when isStandalone is true
|
|
|
|
|
folderClient client.K8sHandler
|
|
|
|
|
folderClientProvider client.K8sHandlerProvider
|
|
|
|
|
|
|
|
|
|
log log.Logger
|
|
|
|
|
reg prometheus.Registerer
|
|
|
|
|
isStandalone bool // skips any handling including anything to do with legacy storage
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func RegisterAPIService(
|
|
|
|
@@ -128,9 +135,11 @@ func RegisterAPIService(
|
|
|
|
|
namespacer := request.GetNamespaceMapper(cfg)
|
|
|
|
|
legacyDashboardSearcher := legacysearcher.NewDashboardSearchClient(dashStore, sorter)
|
|
|
|
|
folderClient := client.NewK8sHandler(dual, request.GetNamespaceMapper(cfg), folders.FolderResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashStore, userService, unified, sorter, features)
|
|
|
|
|
builder := &DashboardsAPIBuilder{
|
|
|
|
|
log: log.New("grafana-apiserver.dashboards"),
|
|
|
|
|
|
|
|
|
|
dashLog := log.New("grafana-apiserver.dashboards")
|
|
|
|
|
builder := &DashboardsAPIBuilder{
|
|
|
|
|
log: dashLog,
|
|
|
|
|
authorizer: newLegacyAuthorizer(accessControl, dashLog),
|
|
|
|
|
dashboardService: dashboardService,
|
|
|
|
|
dashboardPermissions: dashboardPermissions,
|
|
|
|
|
dashboardPermissionsSvc: dashboardPermissionsSvc,
|
|
|
|
@@ -166,6 +175,37 @@ func RegisterAPIService(
|
|
|
|
|
return builder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewAPIService(ac claims.AccessClient, features featuremgmt.FeatureToggles, folderClientProvider client.K8sHandlerProvider, datasourceProvider schemaversion.DataSourceInfoProvider, pluginStore *pluginstore.Service) *DashboardsAPIBuilder {
|
|
|
|
|
// TODO: Plugin store will soon be removed,
|
|
|
|
|
// as the cases for plugin fetching is not needed. Keeping it now to not break implementation
|
|
|
|
|
if pluginStore == nil {
|
|
|
|
|
panic("pluginStore is nil")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger := log.New("grafana-apiserver.dashboards")
|
|
|
|
|
|
|
|
|
|
migration.Initialize(datasourceProvider, &PluginStorePanelProvider{
|
|
|
|
|
pluginStore: pluginStore,
|
|
|
|
|
buildVersion: "unknown",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return &DashboardsAPIBuilder{
|
|
|
|
|
log: logger,
|
|
|
|
|
reg: prometheus.NewRegistry(),
|
|
|
|
|
|
|
|
|
|
cfg: &setting.Cfg{
|
|
|
|
|
MinRefreshInterval: "10s",
|
|
|
|
|
},
|
|
|
|
|
accessClient: ac,
|
|
|
|
|
authorizer: authsvc.NewResourceAuthorizer(ac),
|
|
|
|
|
features: features,
|
|
|
|
|
dashboardService: &dashsvc.DashboardServiceImpl{}, // for validation helpers only
|
|
|
|
|
folderClientProvider: folderClientProvider,
|
|
|
|
|
|
|
|
|
|
isStandalone: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *DashboardsAPIBuilder) GetGroupVersions() []schema.GroupVersion {
|
|
|
|
|
if featuremgmt.AnyEnabled(b.features, featuremgmt.FlagDashboardNewLayouts) {
|
|
|
|
|
// If dashboards v2 is enabled, we want to use v2beta1 as the default API version.
|
|
|
|
@@ -304,12 +344,12 @@ func (b *DashboardsAPIBuilder) validateCreate(ctx context.Context, a admission.A
|
|
|
|
|
// Validate folder existence if specified
|
|
|
|
|
if !a.IsDryRun() && accessor.GetFolder() != "" {
|
|
|
|
|
if err := b.validateFolderExists(ctx, accessor.GetFolder(), id.GetOrgID()); err != nil {
|
|
|
|
|
return apierrors.NewNotFound(folders.FolderResourceInfo.GroupResource(), accessor.GetFolder())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate quota
|
|
|
|
|
if !a.IsDryRun() {
|
|
|
|
|
if !b.isStandalone && !a.IsDryRun() {
|
|
|
|
|
params := "a.ScopeParameters{}
|
|
|
|
|
params.OrgID = id.GetOrgID()
|
|
|
|
|
internalId, err := id.GetInternalID()
|
|
|
|
@@ -388,9 +428,24 @@ func (b *DashboardsAPIBuilder) validateUpdate(ctx context.Context, a admission.A
|
|
|
|
|
// validateFolderExists checks if a folder exists
|
|
|
|
|
func (b *DashboardsAPIBuilder) validateFolderExists(ctx context.Context, folderUID string, orgID int64) error {
|
|
|
|
|
// Check if folder exists using the folder store
|
|
|
|
|
_, err := b.folderClient.Get(ctx, folderUID, orgID, metav1.GetOptions{})
|
|
|
|
|
|
|
|
|
|
var folderClient client.K8sHandler
|
|
|
|
|
if b.isStandalone {
|
|
|
|
|
ns, err := request.NamespaceInfoFrom(ctx, false)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
folderClient = b.folderClientProvider.GetOrCreateHandler(ns.Value)
|
|
|
|
|
} else {
|
|
|
|
|
folderClient = b.folderClient
|
|
|
|
|
}
|
|
|
|
|
_, err := folderClient.Get(ctx, folderUID, orgID, metav1.GetOptions{})
|
|
|
|
|
// Check if the error is a context deadline exceeded error
|
|
|
|
|
if err != nil {
|
|
|
|
|
// historically, we returned a more verbose error with folder name when its not found, below just keeps that behavior
|
|
|
|
|
if apierrors.IsNotFound(err) {
|
|
|
|
|
return apierrors.NewNotFound(folders.FolderResourceInfo.GroupResource(), folderUID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -426,9 +481,13 @@ func (b *DashboardsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver
|
|
|
|
|
storageOpts := apistore.StorageOptions{
|
|
|
|
|
EnableFolderSupport: true,
|
|
|
|
|
RequireDeprecatedInternalID: true,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if b.isStandalone {
|
|
|
|
|
// TODO: Sets default root permissions
|
|
|
|
|
} else {
|
|
|
|
|
// Sets default root permissions
|
|
|
|
|
Permissions: b.dashboardPermissions.SetDefaultPermissionsAfterCreate,
|
|
|
|
|
storageOpts.Permissions = b.dashboardPermissions.SetDefaultPermissionsAfterCreate
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Split dashboards when they are large
|
|
|
|
@@ -525,6 +584,16 @@ func (b *DashboardsAPIBuilder) storageForVersion(
|
|
|
|
|
storage := map[string]rest.Storage{}
|
|
|
|
|
apiGroupInfo.VersionedResourcesStorageMap[dashboards.GroupVersion().Version] = storage
|
|
|
|
|
|
|
|
|
|
if b.isStandalone {
|
|
|
|
|
store, err := grafanaregistry.NewRegistryStore(opts.Scheme, dashboards, opts.OptsGetter)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
storage[dashboards.StoragePath()] = store
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
legacyStore, err := b.legacy.NewStore(dashboards, opts.Scheme, opts.OptsGetter, b.reg, b.dashboardPermissions, b.accessClient)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
@@ -607,7 +676,7 @@ func (b *DashboardsAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.API
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
|
|
|
|
return GetAuthorizer(b.accessControl, b.log)
|
|
|
|
|
return b.authorizer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *DashboardsAPIBuilder) verifyFolderAccessPermissions(ctx context.Context, user identity.Requester, folderIds ...string) error {
|
|
|
|
|