K8s/Dashboards: Generate Dashboards k8s APIs using Grafana App SDK (#99966)
* Generate Dashboard kinds with `grafana-app-sdk` Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> * Hack together a fix for invalid TS codegen for v0 & v1 Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> * Address Go linter issues Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> * Address TS linter issues Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> * Add new app to CODEOWNERS Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> * Fix a couple of issues detected by tests Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> * Update OpenAPI definitions and test files Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> * Remove title from Dashboard v1alpha1 spec Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> * Remove unused CUE schemas Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> * remove unrelated files * allow any in the generated betterer * Add a comment explaining why we don't use deepcopy-gen Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> * Default to v2alpha1 if dashboards v2 FF is enabled Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> --------- Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com> Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
co-authored by
Ryan McKinley
parent
3fffb2872e
commit
ea89a68028
@@ -1,6 +1,7 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -35,7 +36,11 @@ func NewDashboardLargeObjectSupport(scheme *runtime.Scheme) *apistore.BasicLarge
|
||||
case *dashboardV1.Dashboard:
|
||||
reduceUnstructredSpec(&dash.Spec)
|
||||
case *dashboardV2.Dashboard:
|
||||
reduceUnstructredSpec(&dash.Spec)
|
||||
dash.Spec = dashboardV2.DashboardSpec{
|
||||
Title: dash.Spec.Title,
|
||||
Description: dash.Spec.Description,
|
||||
Tags: dash.Spec.Tags,
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported dashboard type %T", obj)
|
||||
}
|
||||
@@ -45,23 +50,16 @@ func NewDashboardLargeObjectSupport(scheme *runtime.Scheme) *apistore.BasicLarge
|
||||
},
|
||||
|
||||
RebuildSpec: func(obj runtime.Object, blob []byte) error {
|
||||
body := commonV0.Unstructured{}
|
||||
err := body.UnmarshalJSON(blob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch dash := obj.(type) {
|
||||
case *dashboardV0.Dashboard:
|
||||
dash.Spec = body
|
||||
return dash.Spec.UnmarshalJSON(blob)
|
||||
case *dashboardV1.Dashboard:
|
||||
dash.Spec = body
|
||||
return dash.Spec.UnmarshalJSON(blob)
|
||||
case *dashboardV2.Dashboard:
|
||||
dash.Spec = body
|
||||
return json.Unmarshal(blob, &dash.Spec)
|
||||
default:
|
||||
return fmt.Errorf("unsupported dashboard type %T", obj)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,7 @@ func (b *DashboardsAPIBuilder) Mutate(ctx context.Context, a admission.Attribute
|
||||
internalID = int64(id)
|
||||
}
|
||||
case *dashboardV2.Dashboard:
|
||||
if id, ok := v.Spec.Object["id"].(float64); ok {
|
||||
delete(v.Spec.Object, "id")
|
||||
internalID = int64(id)
|
||||
}
|
||||
// Noop for V2
|
||||
default:
|
||||
return fmt.Errorf("mutation error: expected to dashboard, got %T", obj)
|
||||
}
|
||||
|
||||
@@ -109,6 +109,16 @@ func RegisterAPIService(
|
||||
}
|
||||
|
||||
func (b *DashboardsAPIBuilder) GetGroupVersions() []schema.GroupVersion {
|
||||
if featuremgmt.AnyEnabled(b.features, featuremgmt.FlagUseV2DashboardsAPI) {
|
||||
// If dashboards v2 is enabled, we want to use v2alpha1 as the default API version.
|
||||
return []schema.GroupVersion{
|
||||
dashboardv2alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
dashboardv0alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
dashboardv1alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (@radiohead): should we switch to v1alpha1 by default?
|
||||
return []schema.GroupVersion{
|
||||
dashboardv0alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
dashboardv1alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
|
||||
@@ -7,11 +7,15 @@ import (
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v1alpha1"
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v2alpha1"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
@@ -158,3 +162,93 @@ func TestDashboardAPIBuilder_Validate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDashboardAPIBuilder_GetGroupVersions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
enabledFeatures []string
|
||||
expected []schema.GroupVersion
|
||||
}{
|
||||
{
|
||||
name: "should return v0alpha1 by default",
|
||||
enabledFeatures: []string{},
|
||||
expected: []schema.GroupVersion{
|
||||
v0alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
v1alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
v2alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should return v0alpha1 as the default if some other feature is enabled",
|
||||
enabledFeatures: []string{
|
||||
featuremgmt.FlagKubernetesDashboards,
|
||||
},
|
||||
expected: []schema.GroupVersion{
|
||||
v0alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
v1alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
v2alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should return v2alpha1 as the default if dashboards v2 is enabled",
|
||||
enabledFeatures: []string{
|
||||
featuremgmt.FlagUseV2DashboardsAPI,
|
||||
},
|
||||
expected: []schema.GroupVersion{
|
||||
v2alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
v0alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
v1alpha1.DashboardResourceInfo.GroupVersion(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
builder := &DashboardsAPIBuilder{
|
||||
features: newMockFeatureToggles(t, tt.enabledFeatures...),
|
||||
}
|
||||
|
||||
require.Equal(t, tt.expected, builder.GetGroupVersions())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockFeatureToggles struct {
|
||||
// We need to make a copy in `GetEnabled` anyway,
|
||||
// so no need to store the original map as map[string]bool.
|
||||
enabledFeatures map[string]struct{}
|
||||
}
|
||||
|
||||
func newMockFeatureToggles(t *testing.T, enabledFeatures ...string) featuremgmt.FeatureToggles {
|
||||
t.Helper()
|
||||
|
||||
res := &mockFeatureToggles{
|
||||
enabledFeatures: make(map[string]struct{}, len(enabledFeatures)),
|
||||
}
|
||||
|
||||
for _, f := range enabledFeatures {
|
||||
res.enabledFeatures[f] = struct{}{}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (m *mockFeatureToggles) IsEnabledGlobally(feature string) bool {
|
||||
_, ok := m.enabledFeatures[feature]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *mockFeatureToggles) IsEnabled(ctx context.Context, feature string) bool {
|
||||
_, ok := m.enabledFeatures[feature]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *mockFeatureToggles) GetEnabled(ctx context.Context) map[string]bool {
|
||||
res := make(map[string]bool, len(m.enabledFeatures))
|
||||
|
||||
for f := range m.enabledFeatures {
|
||||
res[f] = true
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user