Files
grafana/pkg/registry/apis/dashboard/schema_validation.go
Marco de Abreu c47ab101d1 Dashboards: Add Dashboard Schema validation (2) (#103844)
* Activate schema validation and align underlying systems

* update to save as v0 if not the right schema version

* Resolve merge conflicts

* Move RequireApiErrorStatus to tests package

* Add mutation tests

* Fix lint

* Only do min version check if dashboard is v1

* Fix lint and disable provisioning test

* Revert provisioning changes

* Revert more tests and add schema test

* Reran gen

* SQL Dashboard save

* Adjust APIVERSION

* Fixed mutation test

* Add logging on downgrade

---------

Co-authored-by: Marco de Abreu <18629099+marcoabreu@users.noreply.github.com>
Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
2025-04-11 23:05:41 +02:00

74 lines
2.7 KiB
Go

package dashboard
import (
"context"
_ "embed"
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1"
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/featuremgmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ValidateDashboardSpec validates the dashboard spec and throws a detailed error if there are validation errors.
func (b *DashboardsAPIBuilder) ValidateDashboardSpec(ctx context.Context, obj runtime.Object, fieldValidationMode string) (field.ErrorList, error) {
accessor, err := utils.MetaAccessor(obj)
if err != nil {
return nil, fmt.Errorf("error getting meta accessor: %w", err)
}
errorOnSchemaMismatches := false
mode := fieldValidationMode
if mode != metav1.FieldValidationIgnore {
switch obj.(type) {
case *v0alpha1.Dashboard:
errorOnSchemaMismatches = false // Never error for v0
case *v1alpha1.Dashboard:
errorOnSchemaMismatches = !b.features.IsEnabled(ctx, featuremgmt.FlagDashboardDisableSchemaValidationV1)
case *v2alpha1.Dashboard:
errorOnSchemaMismatches = !b.features.IsEnabled(ctx, featuremgmt.FlagDashboardDisableSchemaValidationV2)
default:
return nil, fmt.Errorf("invalid dashboard type: %T", obj)
}
}
if mode == metav1.FieldValidationWarn {
return nil, apierrors.NewBadRequest("Not supported: FieldValidationMode: Warn")
}
alwaysLogSchemaValidationErrors := b.features.IsEnabled(ctx, featuremgmt.FlagDashboardSchemaValidationLogging)
var errors field.ErrorList
var schemaVersionError field.ErrorList
if errorOnSchemaMismatches || alwaysLogSchemaValidationErrors {
switch v := obj.(type) {
case *v0alpha1.Dashboard:
errors, schemaVersionError = v0alpha1.ValidateDashboardSpec(v, alwaysLogSchemaValidationErrors)
case *v1alpha1.Dashboard:
errors, schemaVersionError = v1alpha1.ValidateDashboardSpec(v, alwaysLogSchemaValidationErrors)
case *v2alpha1.Dashboard:
errors = v2alpha1.ValidateDashboardSpec(v)
}
}
if alwaysLogSchemaValidationErrors && len(errors) > 0 {
b.log.Info("Schema validation errors during dashboard validation", "group_version", obj.GetObjectKind().GroupVersionKind().GroupVersion().String(), "name", accessor.GetName(), "errors", errors.ToAggregate().Error(), "schema_version_mismatch", schemaVersionError != nil)
}
if errorOnSchemaMismatches {
if schemaVersionError != nil {
return schemaVersionError, nil
}
if len(errors) > 0 {
return errors, nil
}
}
return nil, nil
}