From 3992ac2ac1b171bc32924ef39b709a27b6dbdb85 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Fri, 21 Feb 2025 06:50:29 -0500 Subject: [PATCH] Dashboard: Multi-version builder (#100305) --- pkg/apis/dashboard/types.go | 7 +- .../{v1alpha1 => v0alpha1}/conversion.go | 23 +- .../{v1alpha1 => v0alpha1}/conversion_test.go | 23 +- .../v0alpha1/zz_generated.conversion.go | 43 ++- .../v1alpha1/zz_generated.conversion.go | 81 ++--- pkg/apis/dashboard/v2alpha1/conversion.go | 34 -- .../dashboard/v2alpha1/conversion_test.go | 67 ---- .../v2alpha1/zz_generated.conversion.go | 81 ++--- pkg/apis/dashboard/zz_generated.deepcopy.go | 17 + pkg/registry/apis/apis.go | 6 - pkg/registry/apis/dashboard/conversion.go | 4 + .../apis/dashboard/conversion_test.go | 4 +- pkg/registry/apis/dashboard/large.go | 2 +- pkg/registry/apis/dashboard/large_test.go | 2 +- pkg/registry/apis/dashboard/latest.go | 7 +- pkg/registry/apis/dashboard/latest_test.go | 2 - .../dashboard/legacy/sql_dashboards_test.go | 14 +- pkg/registry/apis/dashboard/register.go | 293 ++++++++++++++++-- pkg/registry/apis/dashboard/register_test.go | 2 +- pkg/registry/apis/dashboard/restore.go | 5 +- pkg/registry/apis/dashboard/restore_test.go | 2 - .../apis/dashboard/v0alpha1/register.go | 256 --------------- .../apis/dashboard/v0alpha1/register_test.go | 162 ---------- .../apis/dashboard/v1alpha1/register.go | 239 -------------- .../apis/dashboard/v1alpha1/register_test.go | 186 ----------- .../apis/dashboard/v2alpha1/register.go | 239 -------------- .../apis/dashboard/v2alpha1/register_test.go | 186 ----------- pkg/registry/apis/wireset.go | 6 - pkg/storage/unified/apistore/store.go | 42 +-- pkg/storage/unified/apistore/stream.go | 32 +- pkg/storage/unified/apistore/stream_test.go | 34 -- 31 files changed, 480 insertions(+), 1621 deletions(-) rename pkg/apis/dashboard/{v1alpha1 => v0alpha1}/conversion.go (64%) rename pkg/apis/dashboard/{v1alpha1 => v0alpha1}/conversion_test.go (62%) delete mode 100644 pkg/apis/dashboard/v2alpha1/conversion.go delete mode 100644 pkg/apis/dashboard/v2alpha1/conversion_test.go delete mode 100644 pkg/registry/apis/dashboard/v0alpha1/register.go delete mode 100644 pkg/registry/apis/dashboard/v0alpha1/register_test.go delete mode 100644 pkg/registry/apis/dashboard/v1alpha1/register.go delete mode 100644 pkg/registry/apis/dashboard/v1alpha1/register_test.go delete mode 100644 pkg/registry/apis/dashboard/v2alpha1/register.go delete mode 100644 pkg/registry/apis/dashboard/v2alpha1/register_test.go delete mode 100644 pkg/storage/unified/apistore/stream_test.go diff --git a/pkg/apis/dashboard/types.go b/pkg/apis/dashboard/types.go index 1d565fa000e..8aeedcf8384 100644 --- a/pkg/apis/dashboard/types.go +++ b/pkg/apis/dashboard/types.go @@ -16,7 +16,12 @@ type Dashboard struct { metav1.ObjectMeta `json:"metadata,omitempty"` // The dashboard body (unstructured for now) - Spec common.Unstructured `json:"spec"` + Spec DashboardSpec `json:"spec"` +} + +type DashboardSpec struct { + Title string `json:"title"` + common.Unstructured `json:",inline"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/dashboard/v1alpha1/conversion.go b/pkg/apis/dashboard/v0alpha1/conversion.go similarity index 64% rename from pkg/apis/dashboard/v1alpha1/conversion.go rename to pkg/apis/dashboard/v0alpha1/conversion.go index c01b13db29c..89bce0cd6d2 100644 --- a/pkg/apis/dashboard/v1alpha1/conversion.go +++ b/pkg/apis/dashboard/v0alpha1/conversion.go @@ -1,22 +1,31 @@ -package v1alpha1 +package v0alpha1 import ( conversion "k8s.io/apimachinery/pkg/conversion" klog "k8s.io/klog/v2" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + dashboard "github.com/grafana/grafana/pkg/apis/dashboard" "github.com/grafana/grafana/pkg/apis/dashboard/migration" "github.com/grafana/grafana/pkg/apis/dashboard/migration/schemaversion" ) -func Convert_v0alpha1_Unstructured_To_v1alpha1_DashboardSpec(in *common.Unstructured, out *DashboardSpec, s conversion.Scope) error { +func Convert_dashboard_DashboardSpec_To_v0alpha1_Unstructured(in *dashboard.DashboardSpec, out *common.Unstructured, s conversion.Scope) error { + *out = in.Unstructured + if in.Title != "" { + out.Object["title"] = in.Title + } + return nil +} + +func Convert_v0alpha1_Unstructured_To_dashboard_DashboardSpec(in *common.Unstructured, out *dashboard.DashboardSpec, s conversion.Scope) error { out.Unstructured = *in err := migration.Migrate(out.Unstructured.Object, schemaversion.LATEST_VERSION) if err != nil { return err } - t, ok := out.Unstructured.Object["title"].(string) + t, ok := out.Object["title"].(string) if !ok { klog.V(5).Infof("unstructured dashboard title field is not a string %v", t) return nil // skip setting the title if it's not a string in the unstructured object @@ -24,11 +33,3 @@ func Convert_v0alpha1_Unstructured_To_v1alpha1_DashboardSpec(in *common.Unstruct out.Title = t return nil } - -func Convert_v1alpha1_DashboardSpec_To_v0alpha1_Unstructured(in *DashboardSpec, out *common.Unstructured, s conversion.Scope) error { - *out = in.Unstructured - if in.Title != "" { - out.Object["title"] = in.Title - } - return nil -} diff --git a/pkg/apis/dashboard/v1alpha1/conversion_test.go b/pkg/apis/dashboard/v0alpha1/conversion_test.go similarity index 62% rename from pkg/apis/dashboard/v1alpha1/conversion_test.go rename to pkg/apis/dashboard/v0alpha1/conversion_test.go index db91af59698..063c4fc72e0 100644 --- a/pkg/apis/dashboard/v1alpha1/conversion_test.go +++ b/pkg/apis/dashboard/v0alpha1/conversion_test.go @@ -1,4 +1,4 @@ -package v1alpha1 +package v0alpha1 import ( "encoding/json" @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apis/dashboard" ) func TestConvertDashboardVersions(t *testing.T) { @@ -43,7 +44,7 @@ func TestConvertDashboardVersions(t *testing.T) { }, "timepicker": {}, "timezone": "utc", - "title": "New dashboard", + "title": "New", "uid": "be3ymutzclgqod", "version": 1, "weekStart": "" @@ -51,17 +52,17 @@ func TestConvertDashboardVersions(t *testing.T) { object := common.Unstructured{} err := json.Unmarshal(dashboardV0Spec, &object.Object) require.NoError(t, err) - result := DashboardSpec{} - // convert v0 to v1, where we should extract the title & all other elements should be copied - err = Convert_v0alpha1_Unstructured_To_v1alpha1_DashboardSpec(&object, &result, nil) + in := dashboard.DashboardSpec{Title: "New dashboard", Unstructured: object} + result := common.Unstructured{} + err = Convert_dashboard_DashboardSpec_To_v0alpha1_Unstructured(&in, &result, nil) require.NoError(t, err) - require.Equal(t, result.Title, "New dashboard") - require.Equal(t, result.Unstructured, object) - require.Equal(t, result.Unstructured.Object["refresh"], "", "schemaVersion migration not applied. refresh should be an empty string") // now convert back & ensure it is the same - object2 := common.Unstructured{} - err = Convert_v1alpha1_DashboardSpec_To_v0alpha1_Unstructured(&result, &object2, nil) + object2 := *(result.DeepCopy()) + result2 := dashboard.DashboardSpec{} + err = Convert_v0alpha1_Unstructured_To_dashboard_DashboardSpec(&object2, &result2, nil) require.NoError(t, err) - require.Equal(t, object, object2) + require.Equal(t, result2.Title, "New dashboard") + require.Equal(t, result2.Unstructured.Object["schemaVersion"], 41, "schemaVersion migration not applied.") + require.Equal(t, result2.Unstructured.Object["refresh"], "", "schemaVersion migration not applied. refresh should be an empty string") } diff --git a/pkg/apis/dashboard/v0alpha1/zz_generated.conversion.go b/pkg/apis/dashboard/v0alpha1/zz_generated.conversion.go index 70b458a0fa9..324e3a56259 100644 --- a/pkg/apis/dashboard/v0alpha1/zz_generated.conversion.go +++ b/pkg/apis/dashboard/v0alpha1/zz_generated.conversion.go @@ -12,6 +12,7 @@ import ( unsafe "unsafe" datav0alpha1 "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" + commonv0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" dashboard "github.com/grafana/grafana/pkg/apis/dashboard" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" @@ -159,6 +160,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*dashboard.DashboardSpec)(nil), (*commonv0alpha1.Unstructured)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_dashboard_DashboardSpec_To_v0alpha1_Unstructured(a.(*dashboard.DashboardSpec), b.(*commonv0alpha1.Unstructured), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*commonv0alpha1.Unstructured)(nil), (*dashboard.DashboardSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v0alpha1_Unstructured_To_dashboard_DashboardSpec(a.(*commonv0alpha1.Unstructured), b.(*dashboard.DashboardSpec), scope) + }); err != nil { + return err + } return nil } @@ -218,7 +229,9 @@ func Convert_dashboard_AnnotationPermission_To_v0alpha1_AnnotationPermission(in func autoConvert_v0alpha1_Dashboard_To_dashboard_Dashboard(in *Dashboard, out *dashboard.Dashboard, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - out.Spec = in.Spec + if err := Convert_v0alpha1_Unstructured_To_dashboard_DashboardSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } return nil } @@ -229,7 +242,9 @@ func Convert_v0alpha1_Dashboard_To_dashboard_Dashboard(in *Dashboard, out *dashb func autoConvert_dashboard_Dashboard_To_v0alpha1_Dashboard(in *dashboard.Dashboard, out *Dashboard, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - out.Spec = in.Spec + if err := Convert_dashboard_DashboardSpec_To_v0alpha1_Unstructured(&in.Spec, &out.Spec, s); err != nil { + return err + } return nil } @@ -274,7 +289,17 @@ func Convert_dashboard_DashboardAccess_To_v0alpha1_DashboardAccess(in *dashboard func autoConvert_v0alpha1_DashboardList_To_dashboard_DashboardList(in *DashboardList, out *dashboard.DashboardList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]dashboard.Dashboard)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]dashboard.Dashboard, len(*in)) + for i := range *in { + if err := Convert_v0alpha1_Dashboard_To_dashboard_Dashboard(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -285,7 +310,17 @@ func Convert_v0alpha1_DashboardList_To_dashboard_DashboardList(in *DashboardList func autoConvert_dashboard_DashboardList_To_v0alpha1_DashboardList(in *dashboard.DashboardList, out *DashboardList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]Dashboard)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Dashboard, len(*in)) + for i := range *in { + if err := Convert_dashboard_Dashboard_To_v0alpha1_Dashboard(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } diff --git a/pkg/apis/dashboard/v1alpha1/zz_generated.conversion.go b/pkg/apis/dashboard/v1alpha1/zz_generated.conversion.go index fb0b12800df..3c84ddf7156 100644 --- a/pkg/apis/dashboard/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/dashboard/v1alpha1/zz_generated.conversion.go @@ -11,8 +11,7 @@ import ( url "net/url" unsafe "unsafe" - datav0alpha1 "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" - v0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + v0alpha1 "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" dashboard "github.com/grafana/grafana/pkg/apis/dashboard" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" @@ -75,6 +74,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*DashboardSpec)(nil), (*dashboard.DashboardSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_DashboardSpec_To_dashboard_DashboardSpec(a.(*DashboardSpec), b.(*dashboard.DashboardSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*dashboard.DashboardSpec)(nil), (*DashboardSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_dashboard_DashboardSpec_To_v1alpha1_DashboardSpec(a.(*dashboard.DashboardSpec), b.(*DashboardSpec), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*DashboardVersionInfo)(nil), (*dashboard.DashboardVersionInfo)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_DashboardVersionInfo_To_dashboard_DashboardVersionInfo(a.(*DashboardVersionInfo), b.(*dashboard.DashboardVersionInfo), scope) }); err != nil { @@ -160,16 +169,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddConversionFunc((*v0alpha1.Unstructured)(nil), (*DashboardSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v0alpha1_Unstructured_To_v1alpha1_DashboardSpec(a.(*v0alpha1.Unstructured), b.(*DashboardSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*DashboardSpec)(nil), (*v0alpha1.Unstructured)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_DashboardSpec_To_v0alpha1_Unstructured(a.(*DashboardSpec), b.(*v0alpha1.Unstructured), scope) - }); err != nil { - return err - } return nil } @@ -229,7 +228,7 @@ func Convert_dashboard_AnnotationPermission_To_v1alpha1_AnnotationPermission(in func autoConvert_v1alpha1_Dashboard_To_dashboard_Dashboard(in *Dashboard, out *dashboard.Dashboard, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha1_DashboardSpec_To_v0alpha1_Unstructured(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_v1alpha1_DashboardSpec_To_dashboard_DashboardSpec(&in.Spec, &out.Spec, s); err != nil { return err } return nil @@ -242,7 +241,7 @@ func Convert_v1alpha1_Dashboard_To_dashboard_Dashboard(in *Dashboard, out *dashb func autoConvert_dashboard_Dashboard_To_v1alpha1_Dashboard(in *dashboard.Dashboard, out *Dashboard, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_v0alpha1_Unstructured_To_v1alpha1_DashboardSpec(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_dashboard_DashboardSpec_To_v1alpha1_DashboardSpec(&in.Spec, &out.Spec, s); err != nil { return err } return nil @@ -289,17 +288,7 @@ func Convert_dashboard_DashboardAccess_To_v1alpha1_DashboardAccess(in *dashboard func autoConvert_v1alpha1_DashboardList_To_dashboard_DashboardList(in *DashboardList, out *dashboard.DashboardList, s conversion.Scope) error { out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]dashboard.Dashboard, len(*in)) - for i := range *in { - if err := Convert_v1alpha1_Dashboard_To_dashboard_Dashboard(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } + out.Items = *(*[]dashboard.Dashboard)(unsafe.Pointer(&in.Items)) return nil } @@ -310,17 +299,7 @@ func Convert_v1alpha1_DashboardList_To_dashboard_DashboardList(in *DashboardList func autoConvert_dashboard_DashboardList_To_v1alpha1_DashboardList(in *dashboard.DashboardList, out *DashboardList, s conversion.Scope) error { out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Dashboard, len(*in)) - for i := range *in { - if err := Convert_dashboard_Dashboard_To_v1alpha1_Dashboard(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } + out.Items = *(*[]Dashboard)(unsafe.Pointer(&in.Items)) return nil } @@ -329,6 +308,28 @@ func Convert_dashboard_DashboardList_To_v1alpha1_DashboardList(in *dashboard.Das return autoConvert_dashboard_DashboardList_To_v1alpha1_DashboardList(in, out, s) } +func autoConvert_v1alpha1_DashboardSpec_To_dashboard_DashboardSpec(in *DashboardSpec, out *dashboard.DashboardSpec, s conversion.Scope) error { + out.Title = in.Title + out.Unstructured = in.Unstructured + return nil +} + +// Convert_v1alpha1_DashboardSpec_To_dashboard_DashboardSpec is an autogenerated conversion function. +func Convert_v1alpha1_DashboardSpec_To_dashboard_DashboardSpec(in *DashboardSpec, out *dashboard.DashboardSpec, s conversion.Scope) error { + return autoConvert_v1alpha1_DashboardSpec_To_dashboard_DashboardSpec(in, out, s) +} + +func autoConvert_dashboard_DashboardSpec_To_v1alpha1_DashboardSpec(in *dashboard.DashboardSpec, out *DashboardSpec, s conversion.Scope) error { + out.Title = in.Title + out.Unstructured = in.Unstructured + return nil +} + +// Convert_dashboard_DashboardSpec_To_v1alpha1_DashboardSpec is an autogenerated conversion function. +func Convert_dashboard_DashboardSpec_To_v1alpha1_DashboardSpec(in *dashboard.DashboardSpec, out *DashboardSpec, s conversion.Scope) error { + return autoConvert_dashboard_DashboardSpec_To_v1alpha1_DashboardSpec(in, out, s) +} + func autoConvert_v1alpha1_DashboardVersionInfo_To_dashboard_DashboardVersionInfo(in *DashboardVersionInfo, out *dashboard.DashboardVersionInfo, s conversion.Scope) error { out.Version = in.Version out.ParentVersion = in.ParentVersion @@ -466,8 +467,8 @@ func autoConvert_v1alpha1_LibraryPanelSpec_To_dashboard_LibraryPanelSpec(in *Lib out.Description = in.Description out.Options = in.Options out.FieldConfig = in.FieldConfig - out.Datasource = (*datav0alpha1.DataSourceRef)(unsafe.Pointer(in.Datasource)) - out.Targets = *(*[]datav0alpha1.DataQuery)(unsafe.Pointer(&in.Targets)) + out.Datasource = (*v0alpha1.DataSourceRef)(unsafe.Pointer(in.Datasource)) + out.Targets = *(*[]v0alpha1.DataQuery)(unsafe.Pointer(&in.Targets)) return nil } @@ -483,8 +484,8 @@ func autoConvert_dashboard_LibraryPanelSpec_To_v1alpha1_LibraryPanelSpec(in *das out.Description = in.Description out.Options = in.Options out.FieldConfig = in.FieldConfig - out.Datasource = (*datav0alpha1.DataSourceRef)(unsafe.Pointer(in.Datasource)) - out.Targets = *(*[]datav0alpha1.DataQuery)(unsafe.Pointer(&in.Targets)) + out.Datasource = (*v0alpha1.DataSourceRef)(unsafe.Pointer(in.Datasource)) + out.Targets = *(*[]v0alpha1.DataQuery)(unsafe.Pointer(&in.Targets)) return nil } diff --git a/pkg/apis/dashboard/v2alpha1/conversion.go b/pkg/apis/dashboard/v2alpha1/conversion.go deleted file mode 100644 index 9a1818fc0c3..00000000000 --- a/pkg/apis/dashboard/v2alpha1/conversion.go +++ /dev/null @@ -1,34 +0,0 @@ -package v2alpha1 - -import ( - conversion "k8s.io/apimachinery/pkg/conversion" - klog "k8s.io/klog/v2" - - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - "github.com/grafana/grafana/pkg/apis/dashboard/migration" - "github.com/grafana/grafana/pkg/apis/dashboard/migration/schemaversion" -) - -func Convert_v0alpha1_Unstructured_To_v2alpha1_DashboardSpec(in *common.Unstructured, out *DashboardSpec, s conversion.Scope) error { - out.Unstructured = *in - err := migration.Migrate(out.Unstructured.Object, schemaversion.LATEST_VERSION) - if err != nil { - return err - } - - t, ok := out.Unstructured.Object["title"].(string) - if !ok { - klog.V(5).Infof("unstructured dashboard title field is not a string %v", t) - return nil // skip setting the title if it's not a string in the unstructured object - } - out.Title = t - return nil -} - -func Convert_v2alpha1_DashboardSpec_To_v0alpha1_Unstructured(in *DashboardSpec, out *common.Unstructured, s conversion.Scope) error { - *out = in.Unstructured - if in.Title != "" { - out.Object["title"] = in.Title - } - return nil -} diff --git a/pkg/apis/dashboard/v2alpha1/conversion_test.go b/pkg/apis/dashboard/v2alpha1/conversion_test.go deleted file mode 100644 index fdd337c822b..00000000000 --- a/pkg/apis/dashboard/v2alpha1/conversion_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package v2alpha1 - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" - - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" -) - -func TestConvertDashboardVersions(t *testing.T) { - dashboardV0Spec := []byte(`{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "type": "dashboard" - } - ] - }, - "refresh": true, - "description": "", - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": 11711, - "links": [], - "panels": [], - "preload": false, - "schemaVersion": 39, - "tags": [], - "templating": { - "list": [] - }, - "timepicker": {}, - "timezone": "utc", - "title": "New dashboard", - "uid": "be3ymutzclgqod", - "version": 1, - "weekStart": "" -}`) - object := common.Unstructured{} - err := json.Unmarshal(dashboardV0Spec, &object.Object) - require.NoError(t, err) - result := DashboardSpec{} - // convert v0 to v2, where we should extract the title & all other elements should be copied - err = Convert_v0alpha1_Unstructured_To_v2alpha1_DashboardSpec(&object, &result, nil) - require.NoError(t, err) - require.Equal(t, result.Title, "New dashboard") - require.Equal(t, result.Unstructured, object) - require.Equal(t, result.Unstructured.Object["refresh"], "", "schemaVersion migration not applied. refresh should be an empty string") - - // now convert back & ensure it is the same - object2 := common.Unstructured{} - err = Convert_v2alpha1_DashboardSpec_To_v0alpha1_Unstructured(&result, &object2, nil) - require.NoError(t, err) - require.Equal(t, object, object2) -} diff --git a/pkg/apis/dashboard/v2alpha1/zz_generated.conversion.go b/pkg/apis/dashboard/v2alpha1/zz_generated.conversion.go index ca42716b4e2..a3a601271aa 100644 --- a/pkg/apis/dashboard/v2alpha1/zz_generated.conversion.go +++ b/pkg/apis/dashboard/v2alpha1/zz_generated.conversion.go @@ -11,8 +11,7 @@ import ( url "net/url" unsafe "unsafe" - datav0alpha1 "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" - v0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + v0alpha1 "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" dashboard "github.com/grafana/grafana/pkg/apis/dashboard" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" @@ -75,6 +74,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*DashboardSpec)(nil), (*dashboard.DashboardSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v2alpha1_DashboardSpec_To_dashboard_DashboardSpec(a.(*DashboardSpec), b.(*dashboard.DashboardSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*dashboard.DashboardSpec)(nil), (*DashboardSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_dashboard_DashboardSpec_To_v2alpha1_DashboardSpec(a.(*dashboard.DashboardSpec), b.(*DashboardSpec), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*DashboardVersionInfo)(nil), (*dashboard.DashboardVersionInfo)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v2alpha1_DashboardVersionInfo_To_dashboard_DashboardVersionInfo(a.(*DashboardVersionInfo), b.(*dashboard.DashboardVersionInfo), scope) }); err != nil { @@ -160,16 +169,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddConversionFunc((*v0alpha1.Unstructured)(nil), (*DashboardSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v0alpha1_Unstructured_To_v2alpha1_DashboardSpec(a.(*v0alpha1.Unstructured), b.(*DashboardSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*DashboardSpec)(nil), (*v0alpha1.Unstructured)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2alpha1_DashboardSpec_To_v0alpha1_Unstructured(a.(*DashboardSpec), b.(*v0alpha1.Unstructured), scope) - }); err != nil { - return err - } return nil } @@ -229,7 +228,7 @@ func Convert_dashboard_AnnotationPermission_To_v2alpha1_AnnotationPermission(in func autoConvert_v2alpha1_Dashboard_To_dashboard_Dashboard(in *Dashboard, out *dashboard.Dashboard, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_v2alpha1_DashboardSpec_To_v0alpha1_Unstructured(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_v2alpha1_DashboardSpec_To_dashboard_DashboardSpec(&in.Spec, &out.Spec, s); err != nil { return err } return nil @@ -242,7 +241,7 @@ func Convert_v2alpha1_Dashboard_To_dashboard_Dashboard(in *Dashboard, out *dashb func autoConvert_dashboard_Dashboard_To_v2alpha1_Dashboard(in *dashboard.Dashboard, out *Dashboard, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_v0alpha1_Unstructured_To_v2alpha1_DashboardSpec(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_dashboard_DashboardSpec_To_v2alpha1_DashboardSpec(&in.Spec, &out.Spec, s); err != nil { return err } return nil @@ -289,17 +288,7 @@ func Convert_dashboard_DashboardAccess_To_v2alpha1_DashboardAccess(in *dashboard func autoConvert_v2alpha1_DashboardList_To_dashboard_DashboardList(in *DashboardList, out *dashboard.DashboardList, s conversion.Scope) error { out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]dashboard.Dashboard, len(*in)) - for i := range *in { - if err := Convert_v2alpha1_Dashboard_To_dashboard_Dashboard(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } + out.Items = *(*[]dashboard.Dashboard)(unsafe.Pointer(&in.Items)) return nil } @@ -310,17 +299,7 @@ func Convert_v2alpha1_DashboardList_To_dashboard_DashboardList(in *DashboardList func autoConvert_dashboard_DashboardList_To_v2alpha1_DashboardList(in *dashboard.DashboardList, out *DashboardList, s conversion.Scope) error { out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Dashboard, len(*in)) - for i := range *in { - if err := Convert_dashboard_Dashboard_To_v2alpha1_Dashboard(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } + out.Items = *(*[]Dashboard)(unsafe.Pointer(&in.Items)) return nil } @@ -329,6 +308,28 @@ func Convert_dashboard_DashboardList_To_v2alpha1_DashboardList(in *dashboard.Das return autoConvert_dashboard_DashboardList_To_v2alpha1_DashboardList(in, out, s) } +func autoConvert_v2alpha1_DashboardSpec_To_dashboard_DashboardSpec(in *DashboardSpec, out *dashboard.DashboardSpec, s conversion.Scope) error { + out.Title = in.Title + out.Unstructured = in.Unstructured + return nil +} + +// Convert_v2alpha1_DashboardSpec_To_dashboard_DashboardSpec is an autogenerated conversion function. +func Convert_v2alpha1_DashboardSpec_To_dashboard_DashboardSpec(in *DashboardSpec, out *dashboard.DashboardSpec, s conversion.Scope) error { + return autoConvert_v2alpha1_DashboardSpec_To_dashboard_DashboardSpec(in, out, s) +} + +func autoConvert_dashboard_DashboardSpec_To_v2alpha1_DashboardSpec(in *dashboard.DashboardSpec, out *DashboardSpec, s conversion.Scope) error { + out.Title = in.Title + out.Unstructured = in.Unstructured + return nil +} + +// Convert_dashboard_DashboardSpec_To_v2alpha1_DashboardSpec is an autogenerated conversion function. +func Convert_dashboard_DashboardSpec_To_v2alpha1_DashboardSpec(in *dashboard.DashboardSpec, out *DashboardSpec, s conversion.Scope) error { + return autoConvert_dashboard_DashboardSpec_To_v2alpha1_DashboardSpec(in, out, s) +} + func autoConvert_v2alpha1_DashboardVersionInfo_To_dashboard_DashboardVersionInfo(in *DashboardVersionInfo, out *dashboard.DashboardVersionInfo, s conversion.Scope) error { out.Version = in.Version out.ParentVersion = in.ParentVersion @@ -466,8 +467,8 @@ func autoConvert_v2alpha1_LibraryPanelSpec_To_dashboard_LibraryPanelSpec(in *Lib out.Description = in.Description out.Options = in.Options out.FieldConfig = in.FieldConfig - out.Datasource = (*datav0alpha1.DataSourceRef)(unsafe.Pointer(in.Datasource)) - out.Targets = *(*[]datav0alpha1.DataQuery)(unsafe.Pointer(&in.Targets)) + out.Datasource = (*v0alpha1.DataSourceRef)(unsafe.Pointer(in.Datasource)) + out.Targets = *(*[]v0alpha1.DataQuery)(unsafe.Pointer(&in.Targets)) return nil } @@ -483,8 +484,8 @@ func autoConvert_dashboard_LibraryPanelSpec_To_v2alpha1_LibraryPanelSpec(in *das out.Description = in.Description out.Options = in.Options out.FieldConfig = in.FieldConfig - out.Datasource = (*datav0alpha1.DataSourceRef)(unsafe.Pointer(in.Datasource)) - out.Targets = *(*[]datav0alpha1.DataQuery)(unsafe.Pointer(&in.Targets)) + out.Datasource = (*v0alpha1.DataSourceRef)(unsafe.Pointer(in.Datasource)) + out.Targets = *(*[]v0alpha1.DataQuery)(unsafe.Pointer(&in.Targets)) return nil } diff --git a/pkg/apis/dashboard/zz_generated.deepcopy.go b/pkg/apis/dashboard/zz_generated.deepcopy.go index af600f721b6..0c018aa051a 100644 --- a/pkg/apis/dashboard/zz_generated.deepcopy.go +++ b/pkg/apis/dashboard/zz_generated.deepcopy.go @@ -127,6 +127,23 @@ func (in *DashboardList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DashboardSpec) DeepCopyInto(out *DashboardSpec) { + *out = *in + in.Unstructured.DeepCopyInto(&out.Unstructured) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DashboardSpec. +func (in *DashboardSpec) DeepCopy() *DashboardSpec { + if in == nil { + return nil + } + out := new(DashboardSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DashboardVersionInfo) DeepCopyInto(out *DashboardVersionInfo) { *out = *in diff --git a/pkg/registry/apis/apis.go b/pkg/registry/apis/apis.go index 5636df66eb3..aebcfcbbdd0 100644 --- a/pkg/registry/apis/apis.go +++ b/pkg/registry/apis/apis.go @@ -3,9 +3,6 @@ package apiregistry import ( "github.com/grafana/grafana/pkg/registry/apis/alerting/notifications" dashboardinternal "github.com/grafana/grafana/pkg/registry/apis/dashboard" - dashboardv0alpha1 "github.com/grafana/grafana/pkg/registry/apis/dashboard/v0alpha1" - dashboardv1alpha1 "github.com/grafana/grafana/pkg/registry/apis/dashboard/v1alpha1" - dashboardv2alpha1 "github.com/grafana/grafana/pkg/registry/apis/dashboard/v2alpha1" "github.com/grafana/grafana/pkg/registry/apis/dashboardsnapshot" "github.com/grafana/grafana/pkg/registry/apis/datasource" "github.com/grafana/grafana/pkg/registry/apis/featuretoggle" @@ -22,9 +19,6 @@ type Service struct{} // and give each builder the chance to register itself with the main server func ProvideRegistryServiceSink( _ *dashboardinternal.DashboardsAPIBuilder, - _ *dashboardv0alpha1.DashboardsAPIBuilder, - _ *dashboardv1alpha1.DashboardsAPIBuilder, - _ *dashboardv2alpha1.DashboardsAPIBuilder, _ *dashboardsnapshot.SnapshotsAPIBuilder, _ *featuretoggle.FeatureFlagAPIBuilder, _ *datasource.DataSourceAPIBuilder, diff --git a/pkg/registry/apis/dashboard/conversion.go b/pkg/registry/apis/dashboard/conversion.go index c4272a06b26..57fe19ba0e4 100644 --- a/pkg/registry/apis/dashboard/conversion.go +++ b/pkg/registry/apis/dashboard/conversion.go @@ -12,3 +12,7 @@ func ToInternalDashboard(scheme *runtime.Scheme, obj runtime.Object) (*dashboard } return dash, nil } + +func FromInternalDashboard(scheme *runtime.Scheme, dash *dashboard.Dashboard, obj runtime.Object) error { + return scheme.Convert(dash, obj, nil) +} diff --git a/pkg/registry/apis/dashboard/conversion_test.go b/pkg/registry/apis/dashboard/conversion_test.go index 8953e647d9a..a635259d1db 100644 --- a/pkg/registry/apis/dashboard/conversion_test.go +++ b/pkg/registry/apis/dashboard/conversion_test.go @@ -46,9 +46,7 @@ func TestConvertDashboardVersionsToInternal(t *testing.T) { Labels: labels, ResourceVersion: rv, }, - Spec: common.Unstructured{ - Object: body, - }, + Spec: dashboardinternal.DashboardSpec{Title: title, Unstructured: common.Unstructured{Object: body}}, } dashV0 := &dashboardv0alpha1.Dashboard{ ObjectMeta: v1.ObjectMeta{ diff --git a/pkg/registry/apis/dashboard/large.go b/pkg/registry/apis/dashboard/large.go index e54b4d252cb..afec4390421 100644 --- a/pkg/registry/apis/dashboard/large.go +++ b/pkg/registry/apis/dashboard/large.go @@ -28,7 +28,7 @@ func NewDashboardLargeObjectSupport(scheme *runtime.Scheme) *apistore.BasicLarge } old := dash.Spec.Object spec := commonV0.Unstructured{Object: make(map[string]any)} - dash.Spec = spec + dash.Spec = dashboard.DashboardSpec{Unstructured: spec} dash.SetManagedFields(nil) // this could be bigger than the object! keep := []string{"title", "description", "tags", "schemaVersion"} diff --git a/pkg/registry/apis/dashboard/large_test.go b/pkg/registry/apis/dashboard/large_test.go index c5d65fa8f5f..7d5ba1bf200 100644 --- a/pkg/registry/apis/dashboard/large_test.go +++ b/pkg/registry/apis/dashboard/large_test.go @@ -53,7 +53,7 @@ func TestLargeDashboardSupport(t *testing.T) { small, err := json.MarshalIndent(&dash.Spec, "", " ") require.NoError(t, err) require.JSONEq(t, `{ - "schemaVersion": 33, + "schemaVersion": 41, "title": "Panel tests - All panels", "tags": ["gdev","panel-tests","all-panels"] }`, string(small)) diff --git a/pkg/registry/apis/dashboard/latest.go b/pkg/registry/apis/dashboard/latest.go index d221bc3c414..0944bcdf537 100644 --- a/pkg/registry/apis/dashboard/latest.go +++ b/pkg/registry/apis/dashboard/latest.go @@ -10,7 +10,6 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" @@ -25,20 +24,16 @@ type LatestConnector interface { rest.StorageMetadata } -func NewLatestConnector(unified resource.ResourceClient, gr schema.GroupResource, opts generic.RESTOptions, scheme *runtime.Scheme) LatestConnector { +func NewLatestConnector(unified resource.ResourceClient, gr schema.GroupResource) LatestConnector { return &latestREST{ unified: unified, gr: gr, - opts: opts, - scheme: scheme, } } type latestREST struct { unified resource.ResourceClient gr schema.GroupResource - opts generic.RESTOptions - scheme *runtime.Scheme } func (l *latestREST) New() runtime.Object { diff --git a/pkg/registry/apis/dashboard/latest_test.go b/pkg/registry/apis/dashboard/latest_test.go index e4544a2aba7..cdf54985a70 100644 --- a/pkg/registry/apis/dashboard/latest_test.go +++ b/pkg/registry/apis/dashboard/latest_test.go @@ -17,7 +17,6 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/generic" ) func TestLatest(t *testing.T) { @@ -31,7 +30,6 @@ func TestLatest(t *testing.T) { r := &latestREST{ unified: mockClient, gr: gr, - opts: generic.RESTOptions{}, } t.Run("no namespace in context", func(t *testing.T) { diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go index 7a75f7acf3b..ff924388546 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go @@ -6,11 +6,13 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/utils" - "github.com/grafana/grafana/pkg/services/provisioning" "github.com/stretchr/testify/require" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + dashboardinternal "github.com/grafana/grafana/pkg/apis/dashboard" + "github.com/grafana/grafana/pkg/services/provisioning" ) func TestScanRow(t *testing.T) { @@ -50,8 +52,10 @@ func TestScanRow(t *testing.T) { require.NotNil(t, row) require.Equal(t, "Test Dashboard", row.Dash.Name) require.Equal(t, version, row.RV) // rv should be the dashboard version - require.Equal(t, v0alpha1.Unstructured{ - Object: map[string]interface{}{"key": "value"}, + require.Equal(t, dashboardinternal.DashboardSpec{ + Unstructured: common.Unstructured{ + Object: map[string]interface{}{"key": "value"}, + }, }, row.Dash.Spec) require.Equal(t, "default", row.Dash.Namespace) require.Equal(t, &continueToken{orgId: int64(1), id: id}, row.token) diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index 44a789708ca..c223026576f 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -4,77 +4,135 @@ import ( "context" "errors" "fmt" + "maps" + "path" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "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" + "k8s.io/kube-openapi/pkg/validation/spec" claims "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/utils" dashboardinternal "github.com/grafana/grafana/pkg/apis/dashboard" dashboardv0alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" dashboardv1alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v1alpha1" dashboardv2alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v2alpha1" + grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" + grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" + "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" "github.com/grafana/grafana/pkg/services/apiserver/builder" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/provisioning" + "github.com/grafana/grafana/pkg/services/search/sort" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/legacysql" + "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" + "github.com/grafana/grafana/pkg/storage/unified/apistore" + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/prometheus/client_golang/prometheus" ) var ( - _ builder.APIGroupBuilder = (*DashboardsAPIBuilder)(nil) - _ builder.OpenAPIPostProcessor = (*DashboardsAPIBuilder)(nil) + _ builder.APIGroupBuilder = (*DashboardsAPIBuilder)(nil) + _ builder.APIGroupVersionsProvider = (*DashboardsAPIBuilder)(nil) + _ builder.OpenAPIPostProcessor = (*DashboardsAPIBuilder)(nil) + _ builder.APIGroupRouteProvider = (*DashboardsAPIBuilder)(nil) ) // This is used just so wire has something unique to return type DashboardsAPIBuilder struct { - ProvisioningDashboardService dashboards.DashboardProvisioningService + dashboardService dashboards.DashboardService + features featuremgmt.FeatureToggles + + accessControl accesscontrol.AccessControl + legacy *DashboardStorage + unified resource.ResourceClient + dashboardProvisioningService dashboards.DashboardProvisioningService + scheme *runtime.Scheme + search *SearchHandler + + log log.Logger + reg prometheus.Registerer } func RegisterAPIService( + cfg *setting.Cfg, features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, + dashboardService dashboards.DashboardService, provisioningDashboardService dashboards.DashboardProvisioningService, + accessControl accesscontrol.AccessControl, + provisioning provisioning.ProvisioningService, + dashStore dashboards.Store, + reg prometheus.Registerer, + sql db.DB, + tracing *tracing.TracingService, + unified resource.ResourceClient, + dual dualwrite.Service, + sorter sort.Service, ) *DashboardsAPIBuilder { + softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) + dbp := legacysql.NewDatabaseProvider(sql) + namespacer := request.GetNamespaceMapper(cfg) + legacyDashboardSearcher := legacysearcher.NewDashboardSearchClient(dashStore, sorter) builder := &DashboardsAPIBuilder{ - ProvisioningDashboardService: provisioningDashboardService, + log: log.New("grafana-apiserver.dashboards"), + + dashboardService: dashboardService, + features: features, + accessControl: accessControl, + unified: unified, + dashboardProvisioningService: provisioningDashboardService, + search: NewSearchHandler(tracing, dual, legacyDashboardSearcher, unified, features), + + legacy: &DashboardStorage{ + Resource: dashboardinternal.DashboardResourceInfo, + Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete, sorter), + TableConverter: dashboardinternal.DashboardResourceInfo.TableConverter(), + Features: features, + }, + reg: reg, } apiregistration.RegisterAPI(builder) return builder } -func (b *DashboardsAPIBuilder) GetGroupVersion() schema.GroupVersion { - return dashboardinternal.DashboardResourceInfo.GroupVersion() -} - -func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer { - return nil // no authorizer -} - -func (b *DashboardsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { - if err := dashboardinternal.AddToScheme(scheme); err != nil { - return err - } - return scheme.SetVersionPriority( +func (b *DashboardsAPIBuilder) GetGroupVersions() []schema.GroupVersion { + return []schema.GroupVersion{ dashboardv0alpha1.DashboardResourceInfo.GroupVersion(), dashboardv1alpha1.DashboardResourceInfo.GroupVersion(), dashboardv2alpha1.DashboardResourceInfo.GroupVersion(), - ) + } } -func (b *DashboardsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error { - return nil -} - -func (b *DashboardsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { - return nil -} - -func (b *DashboardsAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) { - return oas, nil +func (b *DashboardsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { + b.scheme = scheme + if err := dashboardinternal.AddToScheme(scheme); err != nil { + return err + } + if err := dashboardv0alpha1.AddToScheme(scheme); err != nil { + return err + } + if err := dashboardv1alpha1.AddToScheme(scheme); err != nil { + return err + } + if err := dashboardv2alpha1.AddToScheme(scheme); err != nil { + return err + } + return scheme.SetVersionPriority(b.GetGroupVersions()...) } // Validate will prevent deletion of provisioned dashboards, unless the grace period is set to 0, indicating a force deletion @@ -93,7 +151,7 @@ func (b *DashboardsAPIBuilder) Validate(ctx context.Context, a admission.Attribu return fmt.Errorf("%v: %w", "failed to parse namespace", err) } - provisioningData, err := b.ProvisioningDashboardService.GetProvisionedDashboardDataByDashboardUID(ctx, nsInfo.OrgID, a.GetName()) + provisioningData, err := b.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardUID(ctx, nsInfo.OrgID, a.GetName()) if err != nil { if errors.Is(err, dashboards.ErrProvisionedDashboardNotFound) { return nil @@ -110,3 +168,178 @@ func (b *DashboardsAPIBuilder) Validate(ctx context.Context, a admission.Attribu return nil } + +// Mutate removes any internal ID set in the spec & adds it as a label +func (b *DashboardsAPIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { + op := a.GetOperation() + if op != admission.Create && op != admission.Update { + return nil + } + obj := a.GetObject() + dash, ok := obj.(*dashboardinternal.Dashboard) + if !ok { + return fmt.Errorf("mutation error: expected *dashboardinternal.Dashboard, got %T", obj) + } + + if id, ok := dash.Spec.Object["id"].(float64); ok { + delete(dash.Spec.Object, "id") + if id != 0 { + meta, err := utils.MetaAccessor(obj) + if err != nil { + return err + } + meta.SetDeprecatedInternalID(int64(id)) // nolint:staticcheck + } + } + + return nil +} + +func (b *DashboardsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error { + internalDashResourceInfo := dashboardinternal.DashboardResourceInfo + + legacyStore, err := b.legacy.NewStore(opts.Scheme, opts.OptsGetter, b.reg) + if err != nil { + return err + } + + storageOpts := apistore.StorageOptions{ + RequireDeprecatedInternalID: true, + } + + // Split dashboards when they are large + var largeObjects apistore.LargeObjectSupport + if b.legacy.Features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageBigObjectsSupport) { + largeObjects = NewDashboardLargeObjectSupport(opts.Scheme) + storageOpts.LargeObjectSupport = largeObjects + } + opts.StorageOptions(internalDashResourceInfo.GroupResource(), storageOpts) + + // v0alpha1 + storage, err := b.storageForVersion(opts, legacyStore, largeObjects, func() runtime.Object { + return &dashboardv0alpha1.DashboardWithAccessInfo{} + }) + if err != nil { + return err + } + apiGroupInfo.VersionedResourcesStorageMap[dashboardv0alpha1.VERSION] = storage + + // v1alpha1 + storage, err = b.storageForVersion(opts, legacyStore, largeObjects, func() runtime.Object { + return &dashboardv1alpha1.DashboardWithAccessInfo{} + }) + if err != nil { + return err + } + apiGroupInfo.VersionedResourcesStorageMap[dashboardv1alpha1.VERSION] = storage + + // v2alpha1 + storage, err = b.storageForVersion(opts, legacyStore, largeObjects, func() runtime.Object { + return &dashboardv2alpha1.DashboardWithAccessInfo{} + }) + if err != nil { + return err + } + apiGroupInfo.VersionedResourcesStorageMap[dashboardv2alpha1.VERSION] = storage + + return nil +} + +func (b *DashboardsAPIBuilder) storageForVersion( + opts builder.APIGroupOptions, + legacyStore grafanarest.LegacyStorage, + largeObjects apistore.LargeObjectSupport, + newDTOFunc func() runtime.Object, +) (map[string]rest.Storage, error) { + var ( + err error + scheme = opts.Scheme + dualWriteBuilder = opts.DualWriteBuilder + internalDashResourceInfo = dashboardinternal.DashboardResourceInfo + libraryPanelResourceInfo = dashboardinternal.LibraryPanelResourceInfo + ) + + storage := map[string]rest.Storage{} + storage[internalDashResourceInfo.StoragePath()] = legacyStore + + // Dual writes if a RESTOptionsGetter is provided + if dualWriteBuilder != nil { + store, err := grafanaregistry.NewRegistryStore(scheme, internalDashResourceInfo, opts.OptsGetter) + if err != nil { + return nil, err + } + storage[internalDashResourceInfo.StoragePath()], err = dualWriteBuilder(internalDashResourceInfo.GroupResource(), legacyStore, store) + if err != nil { + return nil, err + } + } + + if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesRestore) { + storage[internalDashResourceInfo.StoragePath("restore")] = NewRestoreConnector( + b.unified, + internalDashResourceInfo.GroupResource(), + ) + + storage[internalDashResourceInfo.StoragePath("latest")] = NewLatestConnector( + b.unified, + internalDashResourceInfo.GroupResource(), + ) + } + + // Register the DTO endpoint that will consolidate all dashboard bits + storage[internalDashResourceInfo.StoragePath("dto")], err = NewDTOConnector( + storage[internalDashResourceInfo.StoragePath()], + largeObjects, + b.legacy.Access, + b.unified, + b.accessControl, + scheme, + newDTOFunc, + ) + if err != nil { + return nil, err + } + + // Expose read only library panels + storage[libraryPanelResourceInfo.StoragePath()] = &LibraryPanelStore{ + Access: b.legacy.Access, + ResourceInfo: dashboardinternal.LibraryPanelResourceInfo, + } + + return storage, nil +} + +func (b *DashboardsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { + return func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { + defs := dashboardv0alpha1.GetOpenAPIDefinitions(ref) + maps.Copy(defs, dashboardv1alpha1.GetOpenAPIDefinitions(ref)) + maps.Copy(defs, dashboardv2alpha1.GetOpenAPIDefinitions(ref)) + return defs + } +} + +func (b *DashboardsAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) { + // The plugin description + oas.Info.Description = "Grafana dashboards as resources" + + for _, gv := range b.GetGroupVersions() { + version := gv.Version + // Hide cluster-scoped resources + root := path.Join("/apis/", dashboardinternal.GROUP, version) + delete(oas.Paths.Paths, path.Join(root, "dashboards")) + delete(oas.Paths.Paths, path.Join(root, "watch", "dashboards")) + + if version == dashboardv0alpha1.VERSION { + sub := oas.Paths.Paths[path.Join(root, "search", "{name}")] + oas.Paths.Paths[path.Join(root, "search")] = sub + delete(oas.Paths.Paths, path.Join(root, "search", "{name}")) + } + } + + return oas, nil +} + +func (b *DashboardsAPIBuilder) GetAPIRoutes() *builder.APIRoutes { + defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} }) + return b.search.GetAPIRoutes(defs) +} diff --git a/pkg/registry/apis/dashboard/register_test.go b/pkg/registry/apis/dashboard/register_test.go index 31f3c2d801a..d218c0727c6 100644 --- a/pkg/registry/apis/dashboard/register_test.go +++ b/pkg/registry/apis/dashboard/register_test.go @@ -128,7 +128,7 @@ func TestDashboardAPIBuilder_Validate(t *testing.T) { fakeService := &dashboards.FakeDashboardProvisioning{} fakeService.On("GetProvisionedDashboardDataByDashboardUID", mock.Anything, mock.Anything, mock.Anything).Return(tt.dashboardResponse, tt.dashboardErrorResponse).Once() b := &DashboardsAPIBuilder{ - ProvisioningDashboardService: fakeService, + dashboardProvisioningService: fakeService, } err := b.Validate(context.Background(), admission.NewAttributesRecord( tt.inputObj, diff --git a/pkg/registry/apis/dashboard/restore.go b/pkg/registry/apis/dashboard/restore.go index 501139305c0..839516cbb31 100644 --- a/pkg/registry/apis/dashboard/restore.go +++ b/pkg/registry/apis/dashboard/restore.go @@ -11,7 +11,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" @@ -25,18 +24,16 @@ type RestoreConnector interface { rest.StorageMetadata } -func NewRestoreConnector(unified resource.ResourceClient, gr schema.GroupResource, opts generic.RESTOptions) RestoreConnector { +func NewRestoreConnector(unified resource.ResourceClient, gr schema.GroupResource) RestoreConnector { return &restoreREST{ unified: unified, gr: gr, - opts: opts, } } type restoreREST struct { unified resource.ResourceClient gr schema.GroupResource - opts generic.RESTOptions } func (r *restoreREST) New() runtime.Object { diff --git a/pkg/registry/apis/dashboard/restore_test.go b/pkg/registry/apis/dashboard/restore_test.go index 035e085e8b1..26edde534ef 100644 --- a/pkg/registry/apis/dashboard/restore_test.go +++ b/pkg/registry/apis/dashboard/restore_test.go @@ -17,7 +17,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/generic" ) type mockResourceClient struct { @@ -58,7 +57,6 @@ func TestRestore(t *testing.T) { r := &restoreREST{ unified: mockClient, gr: gr, - opts: generic.RESTOptions{}, } t.Run("no namespace in context", func(t *testing.T) { diff --git a/pkg/registry/apis/dashboard/v0alpha1/register.go b/pkg/registry/apis/dashboard/v0alpha1/register.go deleted file mode 100644 index e8146dbfb52..00000000000 --- a/pkg/registry/apis/dashboard/v0alpha1/register.go +++ /dev/null @@ -1,256 +0,0 @@ -package v0alpha1 - -import ( - "context" - "fmt" - - "github.com/prometheus/client_golang/prometheus" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "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" - "k8s.io/kube-openapi/pkg/validation/spec" - - "github.com/grafana/grafana/pkg/apimachinery/utils" - dashboardinternal "github.com/grafana/grafana/pkg/apis/dashboard" - dashboardv0alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" - grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/registry/apis/dashboard" - "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" - "github.com/grafana/grafana/pkg/services/apiserver/builder" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/provisioning" - "github.com/grafana/grafana/pkg/services/search/sort" - "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/storage/legacysql" - "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" - "github.com/grafana/grafana/pkg/storage/unified/apistore" - "github.com/grafana/grafana/pkg/storage/unified/resource" -) - -var ( - _ builder.APIGroupBuilder = (*DashboardsAPIBuilder)(nil) - _ builder.OpenAPIPostProcessor = (*DashboardsAPIBuilder)(nil) -) - -// This is used just so wire has something unique to return -type DashboardsAPIBuilder struct { - dashboard.DashboardsAPIBuilder - dashboardService dashboards.DashboardService - features featuremgmt.FeatureToggles - - accessControl accesscontrol.AccessControl - legacy *dashboard.DashboardStorage - search *dashboard.SearchHandler - unified resource.ResourceClient - - log log.Logger - reg prometheus.Registerer -} - -func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, - apiregistration builder.APIRegistrar, - dashboardService dashboards.DashboardService, - provisioningDashboardService dashboards.DashboardProvisioningService, - accessControl accesscontrol.AccessControl, - provisioning provisioning.ProvisioningService, - dashStore dashboards.Store, - reg prometheus.Registerer, - sql db.DB, - tracing *tracing.TracingService, - unified resource.ResourceClient, - dual dualwrite.Service, - sorter sort.Service, -) *DashboardsAPIBuilder { - softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) - dbp := legacysql.NewDatabaseProvider(sql) - namespacer := request.GetNamespaceMapper(cfg) - legacyDashboardSearcher := legacysearcher.NewDashboardSearchClient(dashStore, sorter) - builder := &DashboardsAPIBuilder{ - log: log.New("grafana-apiserver.dashboards.v0alpha1"), - DashboardsAPIBuilder: dashboard.DashboardsAPIBuilder{ - ProvisioningDashboardService: provisioningDashboardService, - }, - dashboardService: dashboardService, - features: features, - accessControl: accessControl, - unified: unified, - search: dashboard.NewSearchHandler(tracing, dual, legacyDashboardSearcher, unified, features), - - legacy: &dashboard.DashboardStorage{ - Resource: dashboardv0alpha1.DashboardResourceInfo, - Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete, sorter), - TableConverter: dashboardv0alpha1.DashboardResourceInfo.TableConverter(), - Features: features, - }, - reg: reg, - } - apiregistration.RegisterAPI(builder) - return builder -} - -func (b *DashboardsAPIBuilder) GetGroupVersion() schema.GroupVersion { - return dashboardv0alpha1.DashboardResourceInfo.GroupVersion() -} - -func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer { - return dashboard.GetAuthorizer(b.dashboardService, b.log) -} - -func (b *DashboardsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { - return dashboardv0alpha1.AddToScheme(scheme) -} - -func (b *DashboardsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error { - scheme := opts.Scheme - - optsGetter := opts.OptsGetter - dualWriteBuilder := opts.DualWriteBuilder - dash := b.legacy.Resource - legacyStore, err := b.legacy.NewStore(scheme, optsGetter, b.reg) - if err != nil { - return err - } - - defaultOpts, err := optsGetter.GetRESTOptions(b.legacy.Resource.GroupResource(), &dashboardinternal.Dashboard{}) - if err != nil { - return err - } - storageOpts := apistore.StorageOptions{ - RequireDeprecatedInternalID: true, - InternalConversion: (func(b []byte, desiredObj runtime.Object) (runtime.Object, error) { - internal := &dashboardinternal.Dashboard{} - obj, _, err := defaultOpts.StorageConfig.Config.Codec.Decode(b, nil, internal) - if err != nil { - return nil, err - } - - err = scheme.Convert(obj, desiredObj, nil) - return desiredObj, err - }), - } - - // Split dashboards when they are large - var largeObjects apistore.LargeObjectSupport - if b.legacy.Features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageBigObjectsSupport) { - largeObjects = dashboard.NewDashboardLargeObjectSupport(scheme) - storageOpts.LargeObjectSupport = largeObjects - } - opts.StorageOptions(dash.GroupResource(), storageOpts) - - storage := map[string]rest.Storage{} - storage[dash.StoragePath()] = legacyStore - - // Dual writes if a RESTOptionsGetter is provided - if dualWriteBuilder != nil { - store, err := grafanaregistry.NewRegistryStore(scheme, dash, optsGetter) - if err != nil { - return err - } - storage[dash.StoragePath()], err = dualWriteBuilder(dash.GroupResource(), legacyStore, store) - if err != nil { - return err - } - } - - if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesRestore) { - storage[dash.StoragePath("restore")] = dashboard.NewRestoreConnector( - b.unified, - dashboardv0alpha1.DashboardResourceInfo.GroupResource(), - defaultOpts, - ) - - storage[dash.StoragePath("latest")] = dashboard.NewLatestConnector( - b.unified, - dashboardv0alpha1.DashboardResourceInfo.GroupResource(), - defaultOpts, - scheme, - ) - } - - // Register the DTO endpoint that will consolidate all dashboard bits - storage[dash.StoragePath("dto")], err = dashboard.NewDTOConnector( - storage[dash.StoragePath()], - largeObjects, - b.legacy.Access, - b.unified, - b.accessControl, - scheme, - func() runtime.Object { return &dashboardv0alpha1.DashboardWithAccessInfo{} }, - ) - if err != nil { - return err - } - - // Expose read only library panels - storage[dashboardv0alpha1.LibraryPanelResourceInfo.StoragePath()] = &dashboard.LibraryPanelStore{ - Access: b.legacy.Access, - ResourceInfo: dashboardv0alpha1.LibraryPanelResourceInfo, - } - - apiGroupInfo.VersionedResourcesStorageMap[dashboardv0alpha1.VERSION] = storage - return nil -} - -func (b *DashboardsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { - return dashboardv0alpha1.GetOpenAPIDefinitions -} - -func (b *DashboardsAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) { - // The plugin description - oas.Info.Description = "Grafana dashboards as resources" - - // The root api URL - root := "/apis/" + b.GetGroupVersion().String() + "/" - - // Hide the ability to list or watch across all tenants - delete(oas.Paths.Paths, root+dashboardv0alpha1.DashboardResourceInfo.GroupResource().Resource) - delete(oas.Paths.Paths, root+"watch/"+dashboardv0alpha1.DashboardResourceInfo.GroupResource().Resource) - - // Resolve the empty name - sub := oas.Paths.Paths[root+"search/{name}"] - oas.Paths.Paths[root+"search"] = sub - delete(oas.Paths.Paths, root+"search/{name}") - - return oas, nil -} - -func (b *DashboardsAPIBuilder) GetAPIRoutes() *builder.APIRoutes { - defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} }) - return b.search.GetAPIRoutes(defs) -} - -// Mutate removes any internal ID set in the spec & adds it as a label -func (b *DashboardsAPIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { - op := a.GetOperation() - if op == admission.Create || op == admission.Update { - obj := a.GetObject() - dash, ok := obj.(*dashboardv0alpha1.Dashboard) - if !ok { - return fmt.Errorf("expected v0alpha1 dashboard") - } - - if id, ok := dash.Spec.Object["id"].(float64); ok { - delete(dash.Spec.Object, "id") - if id != 0 { - meta, err := utils.MetaAccessor(obj) - if err != nil { - return err - } - meta.SetDeprecatedInternalID(int64(id)) // nolint:staticcheck - } - } - } - return nil -} diff --git a/pkg/registry/apis/dashboard/v0alpha1/register_test.go b/pkg/registry/apis/dashboard/v0alpha1/register_test.go deleted file mode 100644 index c9ee7f75d55..00000000000 --- a/pkg/registry/apis/dashboard/v0alpha1/register_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package v0alpha1 - -import ( - "context" - "testing" - - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" - "github.com/grafana/grafana/pkg/services/user" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apiserver/pkg/admission" -) - -func TestDashboardAPIBuilder_Mutate(t *testing.T) { - tests := []struct { - name string - verb string - input *v0alpha1.Dashboard - expected *v0alpha1.Dashboard - }{ - { - name: "should remove id and add as label in create", - verb: "CREATE", - input: &v0alpha1.Dashboard{ - Spec: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(1), - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v0alpha1.Dashboard{ - Spec: common.Unstructured{ - Object: map[string]interface{}{}, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{"grafana.app/deprecatedInternalID": "1"}, - }, - }, - }, - { - name: "should remove id and add as label in update", - verb: "UPDATE", - input: &v0alpha1.Dashboard{ - Spec: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(1), - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v0alpha1.Dashboard{ - Spec: common.Unstructured{ - Object: map[string]interface{}{}, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{"grafana.app/deprecatedInternalID": "1"}, - }, - }, - }, - { - name: "should only remove id ", - verb: "UPDATE", - input: &v0alpha1.Dashboard{ - Spec: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(1), - "testing": "this", - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v0alpha1.Dashboard{ - Spec: common.Unstructured{ - Object: map[string]interface{}{ - "testing": "this", - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{"grafana.app/deprecatedInternalID": "1"}, - }, - }, - }, - { - name: "should not set label if id is 0", - verb: "CREATE", - input: &v0alpha1.Dashboard{ - Spec: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(0), - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v0alpha1.Dashboard{ - Spec: common.Unstructured{ - Object: map[string]interface{}{}, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - }, - } - b := &DashboardsAPIBuilder{} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := b.Mutate(context.Background(), admission.NewAttributesRecord( - tt.input, - nil, - v0alpha1.DashboardResourceInfo.GroupVersionKind(), - "stacks-123", - tt.input.Name, - v0alpha1.DashboardResourceInfo.GroupVersionResource(), - "", - admission.Operation(tt.verb), - nil, - true, - &user.SignedInUser{}, - ), nil) - - require.NoError(t, err) - require.Equal(t, tt.expected, tt.input) - }) - } -} diff --git a/pkg/registry/apis/dashboard/v1alpha1/register.go b/pkg/registry/apis/dashboard/v1alpha1/register.go deleted file mode 100644 index f64cdb45c52..00000000000 --- a/pkg/registry/apis/dashboard/v1alpha1/register.go +++ /dev/null @@ -1,239 +0,0 @@ -package v1alpha1 - -import ( - "context" - "fmt" - - "github.com/prometheus/client_golang/prometheus" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "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" - - "github.com/grafana/grafana/pkg/apimachinery/utils" - dashboardinternal "github.com/grafana/grafana/pkg/apis/dashboard" - dashboardv1alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v1alpha1" - grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/registry/apis/dashboard" - "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" - "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/apiserver/builder" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/provisioning" - "github.com/grafana/grafana/pkg/services/search/sort" - "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/storage/legacysql" - "github.com/grafana/grafana/pkg/storage/unified/apistore" - "github.com/grafana/grafana/pkg/storage/unified/resource" -) - -var ( - _ builder.APIGroupBuilder = (*DashboardsAPIBuilder)(nil) - _ builder.OpenAPIPostProcessor = (*DashboardsAPIBuilder)(nil) -) - -// This is used just so wire has something unique to return -type DashboardsAPIBuilder struct { - dashboard.DashboardsAPIBuilder - dashboardService dashboards.DashboardService - features featuremgmt.FeatureToggles - - accessControl accesscontrol.AccessControl - legacy *dashboard.DashboardStorage - unified resource.ResourceClient - - log log.Logger - reg prometheus.Registerer -} - -func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, - apiregistration builder.APIRegistrar, - dashboardService dashboards.DashboardService, - provisioningDashboardService dashboards.DashboardProvisioningService, - accessControl accesscontrol.AccessControl, - provisioning provisioning.ProvisioningService, - dashStore dashboards.Store, - reg prometheus.Registerer, - sql db.DB, - tracing *tracing.TracingService, - unified resource.ResourceClient, - sorter sort.Service, -) *DashboardsAPIBuilder { - softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) - dbp := legacysql.NewDatabaseProvider(sql) - namespacer := request.GetNamespaceMapper(cfg) - builder := &DashboardsAPIBuilder{ - log: log.New("grafana-apiserver.dashboards.v1alpha1"), - DashboardsAPIBuilder: dashboard.DashboardsAPIBuilder{ - ProvisioningDashboardService: provisioningDashboardService, - }, - dashboardService: dashboardService, - features: features, - accessControl: accessControl, - unified: unified, - - legacy: &dashboard.DashboardStorage{ - Resource: dashboardv1alpha1.DashboardResourceInfo, - Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete, sorter), - TableConverter: dashboardv1alpha1.DashboardResourceInfo.TableConverter(), - Features: features, - }, - reg: reg, - } - apiregistration.RegisterAPI(builder) - return builder -} - -func (b *DashboardsAPIBuilder) GetGroupVersion() schema.GroupVersion { - return dashboardv1alpha1.DashboardResourceInfo.GroupVersion() -} - -func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer { - return dashboard.GetAuthorizer(b.dashboardService, b.log) -} - -func (b *DashboardsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { - return dashboardv1alpha1.AddToScheme(scheme) -} - -func (b *DashboardsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error { - scheme := opts.Scheme - - optsGetter := opts.OptsGetter - dualWriteBuilder := opts.DualWriteBuilder - dash := b.legacy.Resource - legacyStore, err := b.legacy.NewStore(scheme, optsGetter, b.reg) - if err != nil { - return err - } - - defaultOpts, err := optsGetter.GetRESTOptions(b.legacy.Resource.GroupResource(), &dashboardinternal.Dashboard{}) - if err != nil { - return err - } - storageOpts := apistore.StorageOptions{ - RequireDeprecatedInternalID: true, - InternalConversion: (func(b []byte, desiredObj runtime.Object) (runtime.Object, error) { - internal := &dashboardinternal.Dashboard{} - obj, _, err := defaultOpts.StorageConfig.Config.Codec.Decode(b, nil, internal) - if err != nil { - return nil, err - } - - err = scheme.Convert(obj, desiredObj, nil) - return desiredObj, err - }), - } - - // Split dashboards when they are large - var largeObjects apistore.LargeObjectSupport - if b.legacy.Features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageBigObjectsSupport) { - largeObjects = dashboard.NewDashboardLargeObjectSupport(scheme) - storageOpts.LargeObjectSupport = largeObjects - } - opts.StorageOptions(dash.GroupResource(), storageOpts) - - storage := map[string]rest.Storage{} - storage[dash.StoragePath()] = legacyStore - - // Dual writes if a RESTOptionsGetter is provided - if dualWriteBuilder != nil { - store, err := grafanaregistry.NewRegistryStore(scheme, dash, optsGetter) - if err != nil { - return err - } - storage[dash.StoragePath()], err = dualWriteBuilder(dash.GroupResource(), legacyStore, store) - if err != nil { - return err - } - } - - if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesRestore) { - storage[dash.StoragePath("restore")] = dashboard.NewRestoreConnector( - b.unified, - dashboardv1alpha1.DashboardResourceInfo.GroupResource(), - defaultOpts, - ) - - storage[dash.StoragePath("latest")] = dashboard.NewLatestConnector( - b.unified, - dashboardv1alpha1.DashboardResourceInfo.GroupResource(), - defaultOpts, - scheme, - ) - } - - // Register the DTO endpoint that will consolidate all dashboard bits - storage[dash.StoragePath("dto")], err = dashboard.NewDTOConnector( - storage[dash.StoragePath()], - largeObjects, - b.legacy.Access, - b.unified, - b.accessControl, - scheme, - func() runtime.Object { return &dashboardv1alpha1.DashboardWithAccessInfo{} }, - ) - if err != nil { - return err - } - - // Expose read only library panels - storage[dashboardv1alpha1.LibraryPanelResourceInfo.StoragePath()] = &dashboard.LibraryPanelStore{ - Access: b.legacy.Access, - ResourceInfo: dashboardv1alpha1.LibraryPanelResourceInfo, - } - - apiGroupInfo.VersionedResourcesStorageMap[dashboardv1alpha1.VERSION] = storage - return nil -} - -func (b *DashboardsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { - return dashboardv1alpha1.GetOpenAPIDefinitions -} - -func (b *DashboardsAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) { - // The plugin description - oas.Info.Description = "Grafana dashboards as resources" - - // The root api URL - root := "/apis/" + b.GetGroupVersion().String() + "/" - - // Hide the ability to list or watch across all tenants - delete(oas.Paths.Paths, root+dashboardv1alpha1.DashboardResourceInfo.GroupResource().Resource) - delete(oas.Paths.Paths, root+"watch/"+dashboardv1alpha1.DashboardResourceInfo.GroupResource().Resource) - - return oas, nil -} - -// Mutate removes any internal ID set in the spec & adds it as a label -func (b *DashboardsAPIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { - op := a.GetOperation() - if op == admission.Create || op == admission.Update { - obj := a.GetObject() - dash, ok := obj.(*dashboardv1alpha1.Dashboard) - if !ok { - return fmt.Errorf("expected v1alpha1 dashboard") - } - - if id, ok := dash.Spec.Object["id"].(float64); ok { - delete(dash.Spec.Object, "id") - if id != 0 { - meta, err := utils.MetaAccessor(obj) - if err != nil { - return err - } - meta.SetDeprecatedInternalID(int64(id)) // nolint:staticcheck - } - } - } - return nil -} diff --git a/pkg/registry/apis/dashboard/v1alpha1/register_test.go b/pkg/registry/apis/dashboard/v1alpha1/register_test.go deleted file mode 100644 index 3d2f75c94e8..00000000000 --- a/pkg/registry/apis/dashboard/v1alpha1/register_test.go +++ /dev/null @@ -1,186 +0,0 @@ -package v1alpha1 - -import ( - "context" - "testing" - - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - "github.com/grafana/grafana/pkg/apis/dashboard/v1alpha1" - "github.com/grafana/grafana/pkg/services/user" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apiserver/pkg/admission" -) - -func TestDashboardAPIBuilder_Mutate(t *testing.T) { - tests := []struct { - name string - verb string - input *v1alpha1.Dashboard - expected *v1alpha1.Dashboard - }{ - { - name: "should remove id and add as label in create", - verb: "CREATE", - input: &v1alpha1.Dashboard{ - Spec: v1alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(1), - }, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v1alpha1.Dashboard{ - Spec: v1alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{}, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{"grafana.app/deprecatedInternalID": "1"}, - }, - }, - }, - { - name: "should remove id and add as label in update", - verb: "UPDATE", - input: &v1alpha1.Dashboard{ - Spec: v1alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(1), - }, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v1alpha1.Dashboard{ - Spec: v1alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{}, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{"grafana.app/deprecatedInternalID": "1"}, - }, - }, - }, - { - name: "should only remove id ", - verb: "UPDATE", - input: &v1alpha1.Dashboard{ - Spec: v1alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(1), - "testing": "this", - }, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v1alpha1.Dashboard{ - Spec: v1alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{ - "testing": "this", - }, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{"grafana.app/deprecatedInternalID": "1"}, - }, - }, - }, - { - name: "should not set label if id is 0", - verb: "CREATE", - input: &v1alpha1.Dashboard{ - Spec: v1alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(0), - }, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v1alpha1.Dashboard{ - Spec: v1alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{}, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - }, - } - b := &DashboardsAPIBuilder{} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := b.Mutate(context.Background(), admission.NewAttributesRecord( - tt.input, - nil, - v1alpha1.DashboardResourceInfo.GroupVersionKind(), - "stacks-123", - tt.input.Name, - v1alpha1.DashboardResourceInfo.GroupVersionResource(), - "", - admission.Operation(tt.verb), - nil, - true, - &user.SignedInUser{}, - ), nil) - - require.NoError(t, err) - require.Equal(t, tt.expected, tt.input) - }) - } -} diff --git a/pkg/registry/apis/dashboard/v2alpha1/register.go b/pkg/registry/apis/dashboard/v2alpha1/register.go deleted file mode 100644 index 56767e6562b..00000000000 --- a/pkg/registry/apis/dashboard/v2alpha1/register.go +++ /dev/null @@ -1,239 +0,0 @@ -package v2alpha1 - -import ( - "context" - "fmt" - - "github.com/prometheus/client_golang/prometheus" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "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" - - "github.com/grafana/grafana/pkg/apimachinery/utils" - dashboardinternal "github.com/grafana/grafana/pkg/apis/dashboard" - dashboardv2alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v2alpha1" - grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/registry/apis/dashboard" - "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" - "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/apiserver/builder" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/provisioning" - "github.com/grafana/grafana/pkg/services/search/sort" - "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/storage/legacysql" - "github.com/grafana/grafana/pkg/storage/unified/apistore" - "github.com/grafana/grafana/pkg/storage/unified/resource" -) - -var ( - _ builder.APIGroupBuilder = (*DashboardsAPIBuilder)(nil) - _ builder.OpenAPIPostProcessor = (*DashboardsAPIBuilder)(nil) -) - -// This is used just so wire has something unique to return -type DashboardsAPIBuilder struct { - dashboard.DashboardsAPIBuilder - dashboardService dashboards.DashboardService - features featuremgmt.FeatureToggles - - accessControl accesscontrol.AccessControl - legacy *dashboard.DashboardStorage - unified resource.ResourceClient - - log log.Logger - reg prometheus.Registerer -} - -func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, - apiregistration builder.APIRegistrar, - dashboardService dashboards.DashboardService, - provisioningDashboardService dashboards.DashboardProvisioningService, - accessControl accesscontrol.AccessControl, - provisioning provisioning.ProvisioningService, - dashStore dashboards.Store, - reg prometheus.Registerer, - sql db.DB, - tracing *tracing.TracingService, - unified resource.ResourceClient, - sorter sort.Service, -) *DashboardsAPIBuilder { - softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) - dbp := legacysql.NewDatabaseProvider(sql) - namespacer := request.GetNamespaceMapper(cfg) - builder := &DashboardsAPIBuilder{ - log: log.New("grafana-apiserver.dashboards.v2alpha1"), - - DashboardsAPIBuilder: dashboard.DashboardsAPIBuilder{ - ProvisioningDashboardService: provisioningDashboardService, - }, - dashboardService: dashboardService, - features: features, - accessControl: accessControl, - unified: unified, - - legacy: &dashboard.DashboardStorage{ - Resource: dashboardv2alpha1.DashboardResourceInfo, - Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete, sorter), - TableConverter: dashboardv2alpha1.DashboardResourceInfo.TableConverter(), - Features: features, - }, - reg: reg, - } - apiregistration.RegisterAPI(builder) - return builder -} - -func (b *DashboardsAPIBuilder) GetGroupVersion() schema.GroupVersion { - return dashboardv2alpha1.DashboardResourceInfo.GroupVersion() -} - -func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer { - return dashboard.GetAuthorizer(b.dashboardService, b.log) -} - -func (b *DashboardsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { - return dashboardv2alpha1.AddToScheme(scheme) -} - -func (b *DashboardsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error { - scheme := opts.Scheme - - optsGetter := opts.OptsGetter - dualWriteBuilder := opts.DualWriteBuilder - dash := b.legacy.Resource - legacyStore, err := b.legacy.NewStore(scheme, optsGetter, b.reg) - if err != nil { - return err - } - - defaultOpts, err := optsGetter.GetRESTOptions(b.legacy.Resource.GroupResource(), &dashboardinternal.Dashboard{}) - if err != nil { - return err - } - storageOpts := apistore.StorageOptions{ - RequireDeprecatedInternalID: true, - InternalConversion: (func(b []byte, desiredObj runtime.Object) (runtime.Object, error) { - internal := &dashboardinternal.Dashboard{} - obj, _, err := defaultOpts.StorageConfig.Config.Codec.Decode(b, nil, internal) - if err != nil { - return nil, err - } - - err = scheme.Convert(obj, desiredObj, nil) - return desiredObj, err - }), - } - - // Split dashboards when they are large - var largeObjects apistore.LargeObjectSupport - if b.legacy.Features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageBigObjectsSupport) { - largeObjects = dashboard.NewDashboardLargeObjectSupport(scheme) - storageOpts.LargeObjectSupport = largeObjects - } - opts.StorageOptions(dash.GroupResource(), storageOpts) - - storage := map[string]rest.Storage{} - storage[dash.StoragePath()] = legacyStore - - // Dual writes if a RESTOptionsGetter is provided - if dualWriteBuilder != nil { - store, err := grafanaregistry.NewRegistryStore(scheme, dash, optsGetter) - if err != nil { - return err - } - storage[dash.StoragePath()], err = dualWriteBuilder(dash.GroupResource(), legacyStore, store) - if err != nil { - return err - } - } - - if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesRestore) { - storage[dash.StoragePath("restore")] = dashboard.NewRestoreConnector( - b.unified, - dashboardv2alpha1.DashboardResourceInfo.GroupResource(), - defaultOpts, - ) - - storage[dash.StoragePath("latest")] = dashboard.NewLatestConnector( - b.unified, - dashboardv2alpha1.DashboardResourceInfo.GroupResource(), - defaultOpts, - scheme, - ) - } - - // Register the DTO endpoint that will consolidate all dashboard bits - storage[dash.StoragePath("dto")], err = dashboard.NewDTOConnector( - storage[dash.StoragePath()], - largeObjects, - b.legacy.Access, - b.unified, - b.accessControl, - scheme, - func() runtime.Object { return &dashboardv2alpha1.DashboardWithAccessInfo{} }, - ) - if err != nil { - return err - } - - // Expose read only library panels - storage[dashboardv2alpha1.LibraryPanelResourceInfo.StoragePath()] = &dashboard.LibraryPanelStore{ - Access: b.legacy.Access, - ResourceInfo: dashboardv2alpha1.LibraryPanelResourceInfo, - } - - apiGroupInfo.VersionedResourcesStorageMap[dashboardv2alpha1.VERSION] = storage - return nil -} - -func (b *DashboardsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { - return dashboardv2alpha1.GetOpenAPIDefinitions -} - -func (b *DashboardsAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) { - // The plugin description - oas.Info.Description = "Grafana dashboards as resources" - - // The root api URL - root := "/apis/" + b.GetGroupVersion().String() + "/" - - // Hide the ability to list or watch across all tenants - delete(oas.Paths.Paths, root+dashboardv2alpha1.DashboardResourceInfo.GroupResource().Resource) - - return oas, nil -} - -// Mutate removes any internal ID set in the spec & adds it as a label -func (b *DashboardsAPIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { - op := a.GetOperation() - if op == admission.Create || op == admission.Update { - obj := a.GetObject() - dash, ok := obj.(*dashboardv2alpha1.Dashboard) - if !ok { - return fmt.Errorf("expected v2alpha1 dashboard") - } - - if id, ok := dash.Spec.Object["id"].(float64); ok { - delete(dash.Spec.Object, "id") - if id != 0 { - meta, err := utils.MetaAccessor(obj) - if err != nil { - return err - } - meta.SetDeprecatedInternalID(int64(id)) // nolint:staticcheck - } - } - } - return nil -} diff --git a/pkg/registry/apis/dashboard/v2alpha1/register_test.go b/pkg/registry/apis/dashboard/v2alpha1/register_test.go deleted file mode 100644 index 28d5577a869..00000000000 --- a/pkg/registry/apis/dashboard/v2alpha1/register_test.go +++ /dev/null @@ -1,186 +0,0 @@ -package v2alpha1 - -import ( - "context" - "testing" - - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - "github.com/grafana/grafana/pkg/apis/dashboard/v2alpha1" - "github.com/grafana/grafana/pkg/services/user" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apiserver/pkg/admission" -) - -func TestDashboardAPIBuilder_Mutate(t *testing.T) { - tests := []struct { - name string - verb string - input *v2alpha1.Dashboard - expected *v2alpha1.Dashboard - }{ - { - name: "should remove id and add as label in create", - verb: "CREATE", - input: &v2alpha1.Dashboard{ - Spec: v2alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(1), - }, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v2alpha1.Dashboard{ - Spec: v2alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{}, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{"grafana.app/deprecatedInternalID": "1"}, - }, - }, - }, - { - name: "should remove id and add as label in update", - verb: "UPDATE", - input: &v2alpha1.Dashboard{ - Spec: v2alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(1), - }, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v2alpha1.Dashboard{ - Spec: v2alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{}, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{"grafana.app/deprecatedInternalID": "1"}, - }, - }, - }, - { - name: "should only remove id ", - verb: "UPDATE", - input: &v2alpha1.Dashboard{ - Spec: v2alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(1), - "testing": "this", - }, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v2alpha1.Dashboard{ - Spec: v2alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{ - "testing": "this", - }, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{"grafana.app/deprecatedInternalID": "1"}, - }, - }, - }, - { - name: "should not set label if id is 0", - verb: "CREATE", - input: &v2alpha1.Dashboard{ - Spec: v2alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{ - "id": float64(0), - }, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - expected: &v2alpha1.Dashboard{ - Spec: v2alpha1.DashboardSpec{ - Title: "test", - Unstructured: common.Unstructured{ - Object: map[string]interface{}{}, - }, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - }, - }, - } - b := &DashboardsAPIBuilder{} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := b.Mutate(context.Background(), admission.NewAttributesRecord( - tt.input, - nil, - v2alpha1.DashboardResourceInfo.GroupVersionKind(), - "stacks-123", - tt.input.Name, - v2alpha1.DashboardResourceInfo.GroupVersionResource(), - "", - admission.Operation(tt.verb), - nil, - true, - &user.SignedInUser{}, - ), nil) - - require.NoError(t, err) - require.Equal(t, tt.expected, tt.input) - }) - } -} diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index c98c4da67be..ecd0cba0afb 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -5,9 +5,6 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/alerting/notifications" dashboardinternal "github.com/grafana/grafana/pkg/registry/apis/dashboard" - dashboardv0alpha1 "github.com/grafana/grafana/pkg/registry/apis/dashboard/v0alpha1" - dashboardv1alpha1 "github.com/grafana/grafana/pkg/registry/apis/dashboard/v1alpha1" - dashboardv2alpha1 "github.com/grafana/grafana/pkg/registry/apis/dashboard/v2alpha1" "github.com/grafana/grafana/pkg/registry/apis/dashboardsnapshot" "github.com/grafana/grafana/pkg/registry/apis/datasource" "github.com/grafana/grafana/pkg/registry/apis/featuretoggle" @@ -30,9 +27,6 @@ var WireSet = wire.NewSet( // Each must be added here *and* in the ServiceSink above dashboardinternal.RegisterAPIService, - dashboardv0alpha1.RegisterAPIService, - dashboardv1alpha1.RegisterAPIService, - dashboardv2alpha1.RegisterAPIService, dashboardsnapshot.RegisterAPIService, featuretoggle.RegisterAPIService, datasource.RegisterAPIService, diff --git a/pkg/storage/unified/apistore/store.go b/pkg/storage/unified/apistore/store.go index 115443ad099..69fc83f803c 100644 --- a/pkg/storage/unified/apistore/store.go +++ b/pkg/storage/unified/apistore/store.go @@ -6,6 +6,7 @@ package apistore import ( + "bytes" "context" "errors" "fmt" @@ -47,7 +48,6 @@ var _ storage.Interface = (*Storage)(nil) // Optional settings that apply to a single resource type StorageOptions struct { LargeObjectSupport LargeObjectSupport - InternalConversion func([]byte, runtime.Object) (runtime.Object, error) RequireDeprecatedInternalID bool } @@ -148,9 +148,6 @@ func (s *Storage) Versioner() storage.Versioner { } func (s *Storage) convertToObject(data []byte, obj runtime.Object) (runtime.Object, error) { - if s.opts.InternalConversion != nil { - return s.opts.InternalConversion(data, obj) - } obj, _, err := s.codec.Decode(data, nil, obj) return obj, err } @@ -180,7 +177,7 @@ func (s *Storage) Create(ctx context.Context, key string, obj runtime.Object, ou return resource.GetError(rsp.Error) } - if err := copyModifiedObjectToDestination(obj, out); err != nil { + if _, err := s.convertToObject(req.Value, out); err != nil { return err } @@ -294,7 +291,7 @@ func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOption } reporter := apierrors.NewClientErrorReporter(500, "WATCH", "") - decoder := newStreamDecoder(client, s.newFunc, predicate, s.codec, cancelWatch, s.opts.InternalConversion) + decoder := newStreamDecoder(client, s.newFunc, predicate, s.codec, cancelWatch) return watch.NewStreamWatcher(decoder, reporter), nil } @@ -538,15 +535,22 @@ func (s *Storage) GuaranteedUpdate( } if unchanged { - if err := copyModifiedObjectToDestination(updatedObj, destination); err != nil { + var buf bytes.Buffer + if err = s.codec.Encode(updatedObj, &buf); err != nil { + return err + } + if _, err := s.convertToObject(buf.Bytes(), destination); err != nil { return err } return nil } - rv := int64(0) + var ( + value []byte + rv int64 + ) if created { - value, err := s.prepareObjectForStorage(ctx, updatedObj) + value, err = s.prepareObjectForStorage(ctx, updatedObj) if err != nil { return err } @@ -562,10 +566,11 @@ func (s *Storage) GuaranteedUpdate( } rv = rsp2.ResourceVersion } else { - req.Value, err = s.prepareObjectForUpdate(ctx, updatedObj, existingObj) + value, err = s.prepareObjectForUpdate(ctx, updatedObj, existingObj) if err != nil { return err } + req.Value = value rsp2, err := s.store.Update(ctx, req) if err != nil { return resource.GetError(resource.AsErrorResult(err)) @@ -576,11 +581,11 @@ func (s *Storage) GuaranteedUpdate( rv = rsp2.ResourceVersion } - if err := s.versioner.UpdateObject(updatedObj, uint64(rv)); err != nil { + if _, err := s.convertToObject(value, destination); err != nil { return err } - if err := copyModifiedObjectToDestination(updatedObj, destination); err != nil { + if err := s.versioner.UpdateObject(destination, uint64(rv)); err != nil { return err } @@ -634,16 +639,3 @@ func (s *Storage) validateMinimumResourceVersion(minimumResourceVersion string, } return nil } - -func copyModifiedObjectToDestination(updatedObj runtime.Object, destination runtime.Object) error { - u, err := conversion.EnforcePtr(updatedObj) - if err != nil { - return fmt.Errorf("unable to enforce updated object pointer: %w", err) - } - d, err := conversion.EnforcePtr(destination) - if err != nil { - return fmt.Errorf("unable to enforce destination pointer: %w", err) - } - d.Set(u) - return nil -} diff --git a/pkg/storage/unified/apistore/stream.go b/pkg/storage/unified/apistore/stream.go index e38e1a76d16..4ef79e9d99e 100644 --- a/pkg/storage/unified/apistore/stream.go +++ b/pkg/storage/unified/apistore/stream.go @@ -19,33 +19,27 @@ import ( ) type streamDecoder struct { - client resource.ResourceStore_WatchClient - newFunc func() runtime.Object - predicate storage.SelectionPredicate - codec runtime.Codec - cancelWatch context.CancelFunc - done sync.WaitGroup - internalConversion func([]byte, runtime.Object) (runtime.Object, error) + client resource.ResourceStore_WatchClient + newFunc func() runtime.Object + predicate storage.SelectionPredicate + codec runtime.Codec + cancelWatch context.CancelFunc + done sync.WaitGroup } -func newStreamDecoder(client resource.ResourceStore_WatchClient, newFunc func() runtime.Object, predicate storage.SelectionPredicate, codec runtime.Codec, cancelWatch context.CancelFunc, internalConversion func([]byte, runtime.Object) (runtime.Object, error)) *streamDecoder { +func newStreamDecoder(client resource.ResourceStore_WatchClient, newFunc func() runtime.Object, predicate storage.SelectionPredicate, codec runtime.Codec, cancelWatch context.CancelFunc) *streamDecoder { return &streamDecoder{ - client: client, - newFunc: newFunc, - predicate: predicate, - codec: codec, - cancelWatch: cancelWatch, - internalConversion: internalConversion, + client: client, + newFunc: newFunc, + predicate: predicate, + codec: codec, + cancelWatch: cancelWatch, } } func (d *streamDecoder) toObject(w *resource.WatchEvent_Resource) (runtime.Object, error) { var obj runtime.Object var err error - if d.internalConversion != nil { - obj, err = d.internalConversion(w.Value, d.newFunc()) - } else { - obj, _, err = d.codec.Decode(w.Value, nil, d.newFunc()) - } + obj, _, err = d.codec.Decode(w.Value, nil, d.newFunc()) if err == nil { accessor, err := utils.MetaAccessor(obj) if err != nil { diff --git a/pkg/storage/unified/apistore/stream_test.go b/pkg/storage/unified/apistore/stream_test.go deleted file mode 100644 index e707fbda5d8..00000000000 --- a/pkg/storage/unified/apistore/stream_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package apistore - -import ( - "testing" - - "github.com/grafana/grafana/pkg/storage/unified/resource" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" -) - -func TestStreamDecoder(t *testing.T) { - t.Run("toObject should handle internal conversion", func(t *testing.T) { - called := false - internalConversion := func(data []byte, obj runtime.Object) (runtime.Object, error) { - called = true - return obj, nil - } - - decoder := &streamDecoder{ - newFunc: func() runtime.Object { return &unstructured.Unstructured{} }, - internalConversion: internalConversion, - } - - event := &resource.WatchEvent_Resource{ - Value: []byte("test"), - } - - obj, err := decoder.toObject(event) - require.NoError(t, err) - require.NotNil(t, obj) - require.True(t, called, "internal conversion function should have been called") - }) -}