Stars: support running stars in mode 5 (#111754)

This commit is contained in:
Ryan McKinley
2025-09-29 22:28:48 +03:00
committed by GitHub
parent 8638ef5cba
commit 0a06183d84
14 changed files with 585 additions and 44 deletions
@@ -115,6 +115,10 @@ export interface FeatureToggles {
*/
starsFromAPIServer?: boolean;
/**
* Routes stars requests from /api to the /apis endpoint
*/
kubernetesStars?: boolean;
/**
* Enable streaming JSON parser for InfluxDB datasource InfluxQL query language
*/
influxqlStreamingParser?: boolean;
@@ -112,6 +112,7 @@ func (b *APIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupI
if err != nil {
return err
}
stars = &starStorage{store: stars} // wrap List so we only return one value
if b.legacyStars != nil && opts.DualWriteBuilder != nil {
stars, err = opts.DualWriteBuilder(resource.GroupResource(), b.legacyStars, stars)
if err != nil {
+104
View File
@@ -0,0 +1,104 @@
package preferences
import (
"context"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
authlib "github.com/grafana/authlib/types"
preferences "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
)
var _ grafanarest.Storage = (*starStorage)(nil)
type starStorage struct {
store grafanarest.Storage
}
// When using list, we really just want to get the value for the single user
func (s *starStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
switch user.GetIdentityType() {
case authlib.TypeAnonymous:
return s.NewList(), nil
// Get the single user stars
case authlib.TypeUser:
stars := &preferences.StarsList{}
obj, _ := s.store.Get(ctx, "user-"+user.GetIdentifier(), &v1.GetOptions{})
if obj != nil {
s, ok := obj.(*preferences.Stars)
if ok {
stars.Items = []preferences.Stars{*s}
}
}
return stars, nil
default:
return s.store.List(ctx, options)
}
}
// ConvertToTable implements rest.Storage.
func (s *starStorage) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*v1.Table, error) {
return s.store.ConvertToTable(ctx, obj, tableOptions)
}
// Create implements rest.Storage.
func (s *starStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *v1.CreateOptions) (runtime.Object, error) {
return s.store.Create(ctx, obj, createValidation, options)
}
// Delete implements rest.Storage.
func (s *starStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *v1.DeleteOptions) (runtime.Object, bool, error) {
return s.store.Delete(ctx, name, deleteValidation, options)
}
// DeleteCollection implements rest.Storage.
func (s *starStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *v1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
return s.store.DeleteCollection(ctx, deleteValidation, options, listOptions)
}
// Destroy implements rest.Storage.
func (s *starStorage) Destroy() {
s.store.Destroy()
}
// Get implements rest.Storage.
func (s *starStorage) Get(ctx context.Context, name string, options *v1.GetOptions) (runtime.Object, error) {
return s.store.Get(ctx, name, options)
}
// GetSingularName implements rest.Storage.
func (s *starStorage) GetSingularName() string {
return s.store.GetSingularName()
}
// NamespaceScoped implements rest.Storage.
func (s *starStorage) NamespaceScoped() bool {
return s.store.NamespaceScoped()
}
// New implements rest.Storage.
func (s *starStorage) New() runtime.Object {
return s.store.New()
}
// NewList implements rest.Storage.
func (s *starStorage) NewList() runtime.Object {
return s.store.NewList()
}
// Update implements rest.Storage.
func (s *starStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *v1.UpdateOptions) (runtime.Object, bool, error) {
return s.store.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options)
}
+2 -2
View File
@@ -705,7 +705,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
searchHTTPService := searchV2.ProvideSearchHTTPService(searchService)
statsService := statsimpl.ProvideService(cfg, sqlStore, dashboardService, folderimplService, orgService, resourceClient, featureToggles)
gatherer := metrics.ProvideGatherer()
apiAPI := api3.ProvideApi(starService, dashboardService)
apiAPI := api3.ProvideApi(cfg, featureToggles, starService, eventualRestConfigProvider)
anonUserLimitValidatorImpl := validator2.ProvideAnonUserLimitValidator()
anonDeviceService := anonimpl.ProvideAnonymousDeviceService(usageStats, authnService, sqlStore, cfg, orgService, serverLockService, accessControl, routeRegisterImpl, anonUserLimitValidatorImpl)
signingkeysimplService, err := signingkeysimpl.ProvideEmbeddedSigningKeysService(sqlStore, secretsService, remoteCache, routeRegisterImpl)
@@ -1312,7 +1312,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
searchHTTPService := searchV2.ProvideSearchHTTPService(searchService)
statsService := statsimpl.ProvideService(cfg, sqlStore, dashboardService, folderimplService, orgService, resourceClient, featureToggles)
gatherer := metrics.ProvideGathererForTest(registerer)
apiAPI := api3.ProvideApi(starService, dashboardService)
apiAPI := api3.ProvideApi(cfg, featureToggles, starService, eventualRestConfigProvider)
anonUserLimitValidatorImpl := validator2.ProvideAnonUserLimitValidator()
anonDeviceService := anonimpl.ProvideAnonymousDeviceService(usageStats, authnService, sqlStore, cfg, orgService, serverLockService, accessControl, routeRegisterImpl, anonUserLimitValidatorImpl)
signingkeysimplService, err := signingkeysimpl.ProvideEmbeddedSigningKeysService(sqlStore, secretsService, remoteCache, routeRegisterImpl)
+2 -1
View File
@@ -5,8 +5,9 @@ import (
"errors"
"net/http"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
clientrest "k8s.io/client-go/rest"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
)
type RestConfigProvider interface {
+7
View File
@@ -180,6 +180,13 @@ var (
AllowSelfServe: false,
HideFromDocs: true,
},
{
Name: "kubernetesStars",
Description: "Routes stars requests from /api to the /apis endpoint",
Stage: FeatureStageExperimental,
Owner: grafanaAppPlatformSquad,
RequiresRestart: true, // changes the API routing
},
{
Name: "influxqlStreamingParser",
Description: "Enable streaming JSON parser for InfluxDB datasource InfluxQL query language",
+1
View File
@@ -21,6 +21,7 @@ lokiQuerySplitting,GA,@grafana/observability-logs,false,false,true
individualCookiePreferences,experimental,@grafana/grafana-backend-group,false,false,false
influxdbBackendMigration,GA,@grafana/partner-datasources,false,false,true
starsFromAPIServer,experimental,@grafana/grafana-frontend-platform,false,false,true
kubernetesStars,experimental,@grafana/grafana-app-platform-squad,false,true,false
influxqlStreamingParser,experimental,@grafana/partner-datasources,false,false,false
influxdbRunQueriesInParallel,privatePreview,@grafana/partner-datasources,false,false,false
lokiLogsDataplane,experimental,@grafana/observability-logs,false,false,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
21 individualCookiePreferences experimental @grafana/grafana-backend-group false false false
22 influxdbBackendMigration GA @grafana/partner-datasources false false true
23 starsFromAPIServer experimental @grafana/grafana-frontend-platform false false true
24 kubernetesStars experimental @grafana/grafana-app-platform-squad false true false
25 influxqlStreamingParser experimental @grafana/partner-datasources false false false
26 influxdbRunQueriesInParallel privatePreview @grafana/partner-datasources false false false
27 lokiLogsDataplane experimental @grafana/observability-logs false false false
+4
View File
@@ -95,6 +95,10 @@ const (
// populate star status from apiserver
FlagStarsFromAPIServer = "starsFromAPIServer"
// FlagKubernetesStars
// Routes stars requests from /api to the /apis endpoint
FlagKubernetesStars = "kubernetesStars"
// FlagInfluxqlStreamingParser
// Enable streaming JSON parser for InfluxDB datasource InfluxQL query language
FlagInfluxqlStreamingParser = "influxqlStreamingParser"
+13
View File
@@ -2188,6 +2188,19 @@
"requiresRestart": true
}
},
{
"metadata": {
"name": "kubernetesStars",
"resourceVersion": "1759149842036",
"creationTimestamp": "2025-09-29T12:44:02Z"
},
"spec": {
"description": "Routes stars requests from /api to the /apis endpoint",
"stage": "experimental",
"codeowner": "@grafana/grafana-app-platform-squad",
"requiresRestart": true
}
},
{
"metadata": {
"name": "localeFormatPreference",
+44 -31
View File
@@ -1,56 +1,53 @@
package api
import (
"context"
"net/http"
"time"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/star"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
type API struct {
starService star.Service
dashboardService dashboards.DashboardService
logger log.Logger
starService star.Service
client K8sClients
}
func ProvideApi(
cfg *setting.Cfg, // for namespacer
features featuremgmt.FeatureToggles,
starService star.Service,
dashboardService dashboards.DashboardService,
configProvider apiserver.DirectRestConfigProvider,
) *API {
starLogger := log.New("stars.api")
api := &API{
starService: starService,
dashboardService: dashboardService,
logger: starLogger,
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesStars) {
starService = nil // don't use it
}
return api
}
func (api *API) getDashboardHelper(ctx context.Context, orgID int64, id int64, uid string) (*dashboards.Dashboard, response.Response) {
var query dashboards.GetDashboardQuery
if len(uid) > 0 {
query = dashboards.GetDashboardQuery{UID: uid, ID: id, OrgID: orgID}
} else {
query = dashboards.GetDashboardQuery{ID: id, OrgID: orgID}
return &API{
starService: starService,
client: &k8sClients{
namespacer: request.GetNamespaceMapper(cfg),
configProvider: configProvider,
},
}
result, err := api.dashboardService.GetDashboard(ctx, &query)
if err != nil {
return nil, response.Error(http.StatusNotFound, "Dashboard not found", err)
}
return result, nil
}
func (api *API) GetStars(c *contextmodel.ReqContext) response.Response {
if api.starService == nil {
stars, err := api.client.GetStars(c)
if err != nil {
logging.FromContext(c.Req.Context()).With("logger", "star.api").Warn("error", "err", err)
}
return response.JSON(http.StatusOK, stars)
}
query := star.GetUserStarsQuery{
UserID: c.UserID,
}
@@ -86,17 +83,25 @@ func (api *API) StarDashboardByUID(c *contextmodel.ReqContext) response.Response
return response.Error(http.StatusBadRequest, "Invalid dashboard UID", nil)
}
if api.starService == nil {
err := api.client.AddStar(c, uid)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to star dashboard", err)
}
return response.Success("Dashboard starred!")
}
userID, err := identity.UserIdentifier(c.GetID())
if err != nil {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
}
dash, rsp := api.getDashboardHelper(c.Req.Context(), c.GetOrgID(), 0, uid)
dashboardID, rsp := api.client.GetDashboardID(c, uid)
if rsp != nil {
return rsp
}
cmd := star.StarDashboardCommand{UserID: userID, DashboardID: dash.ID, DashboardUID: uid, OrgID: c.GetOrgID(), Updated: time.Now()}
cmd := star.StarDashboardCommand{UserID: userID, DashboardID: dashboardID, DashboardUID: uid, OrgID: c.GetOrgID(), Updated: time.Now()}
if err := api.starService.Add(c.Req.Context(), &cmd); err != nil {
return response.Error(http.StatusInternalServerError, "Failed to star dashboard", err)
@@ -123,6 +128,14 @@ func (api *API) UnstarDashboardByUID(c *contextmodel.ReqContext) response.Respon
return response.Error(http.StatusBadRequest, "Invalid dashboard UID", nil)
}
if api.starService == nil {
err := api.client.RemoveStar(c, uid)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to unstar dashboard", err)
}
return response.Success("Dashboard unstarred")
}
userID, err := identity.UserIdentifier(c.GetID())
if err != nil {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
+6 -4
View File
@@ -8,16 +8,18 @@ import (
"github.com/stretchr/testify/mock"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/star/startest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/web"
)
func TestStarDashboardUID(t *testing.T) {
svc := dashboards.NewFakeDashboardService(t)
svc.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{UID: "test", OrgID: 1}, nil)
api := ProvideApi(startest.NewStarServiceFake(), svc)
client := NewMockK8sClients(t)
client.On("GetDashboardID", mock.Anything, mock.Anything).Return(int64(123), nil)
api := &API{
starService: startest.NewStarServiceFake(),
client: client,
}
testCases := []struct {
name string
+138
View File
@@ -0,0 +1,138 @@
package api
import (
"net/http"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
authlib "github.com/grafana/authlib/types"
dashboardsV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
preferencesV1 "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/apiserver"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
)
//go:generate mockery --name K8sClients --structname MockK8sClients --inpackage --filename client_mock.go --with-expecter
type K8sClients interface {
GetDashboardID(c *contextmodel.ReqContext, uid string) (int64, response.Response)
GetStars(c *contextmodel.ReqContext) ([]string, error)
AddStar(c *contextmodel.ReqContext, uid string) error
RemoveStar(c *contextmodel.ReqContext, uid string) error
}
type k8sClients struct {
namespacer authlib.NamespaceFormatter
configProvider apiserver.DirectRestConfigProvider
}
var (
_ K8sClients = (*k8sClients)(nil)
)
// GetDashboardID implements the K8sClients interface.
func (k *k8sClients) GetDashboardID(c *contextmodel.ReqContext, uid string) (int64, response.Response) {
dyn, err := dynamic.NewForConfig(k.configProvider.GetDirectRestConfig(c))
if err != nil {
return 0, response.Error(http.StatusInternalServerError, "client config", err)
}
client := dyn.Resource(dashboardsV1.GroupVersion.WithResource(dashboardsV1.DASHBOARD_RESOURCE)).Namespace(k.namespacer(c.OrgID))
obj, err := client.Get(c.Req.Context(), uid, v1.GetOptions{})
if err != nil {
return 0, response.Error(http.StatusNotFound, "Dashboard not found", err)
}
dash, err := utils.MetaAccessor(obj)
if err != nil {
return 0, response.Error(http.StatusInternalServerError, "invalid object", err)
}
return dash.GetDeprecatedInternalID(), nil // nolint:staticcheck
}
// GetStars implements K8sClients.
func (k *k8sClients) GetStars(c *contextmodel.ReqContext) ([]string, error) {
dyn, err := dynamic.NewForConfig(k.configProvider.GetDirectRestConfig(c))
if err != nil {
return nil, err
}
client := dyn.Resource(preferencesV1.StarsResourceInfo.GroupVersionResource()).Namespace(k.namespacer(c.OrgID))
ctx := c.Req.Context()
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
obj, _ := client.Get(ctx, "user-"+user.GetIdentifier(), v1.GetOptions{})
if obj != nil {
resources, ok, _ := unstructured.NestedSlice(obj.Object, "spec", "resource")
if ok && resources != nil {
for _, r := range resources {
tmp, ok := r.(map[string]any)
if ok {
g, _, _ := unstructured.NestedString(tmp, "group")
k, _, _ := unstructured.NestedString(tmp, "kind")
if k == "Dashboard" && g == dashboardsV1.APIGroup {
names, _, _ := unstructured.NestedStringSlice(tmp, "names")
return names, nil
}
}
}
}
}
return []string{}, nil
}
// AddStar implements K8sClients.
func (k *k8sClients) AddStar(c *contextmodel.ReqContext, uid string) error {
dyn, err := kubernetes.NewForConfig(k.configProvider.GetDirectRestConfig(c))
if err != nil {
return err
}
ctx := c.Req.Context()
user, err := identity.GetRequester(ctx)
if err != nil {
return err
}
ns := k.namespacer(c.OrgID)
client := dyn.RESTClient()
rsp := client.Put().AbsPath(
"apis", preferencesV1.APIGroup, preferencesV1.APIVersion, "namespaces", ns,
"stars", "user-"+user.GetIdentifier(),
"update", dashboardsV1.APIGroup, dashboardsV1.DashboardKind().Kind(), uid,
).Do(ctx)
return rsp.Error()
}
// RemoveStar implements K8sClients.
func (k *k8sClients) RemoveStar(c *contextmodel.ReqContext, uid string) error {
dyn, err := kubernetes.NewForConfig(k.configProvider.GetDirectRestConfig(c))
if err != nil {
return err
}
ctx := c.Req.Context()
user, err := identity.GetRequester(ctx)
if err != nil {
return err
}
ns := k.namespacer(c.OrgID)
client := dyn.RESTClient()
rsp := client.Delete().AbsPath(
"apis", preferencesV1.APIGroup, preferencesV1.APIVersion, "namespaces", ns,
"stars", "user-"+user.GetIdentifier(),
"update", dashboardsV1.APIGroup, dashboardsV1.DashboardKind().Kind(), uid,
).Do(ctx)
return rsp.Error()
}
+248
View File
@@ -0,0 +1,248 @@
// Code generated by mockery v2.53.4. DO NOT EDIT.
package api
import (
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
mock "github.com/stretchr/testify/mock"
response "github.com/grafana/grafana/pkg/api/response"
)
// MockK8sClients is an autogenerated mock type for the K8sClients type
type MockK8sClients struct {
mock.Mock
}
type MockK8sClients_Expecter struct {
mock *mock.Mock
}
func (_m *MockK8sClients) EXPECT() *MockK8sClients_Expecter {
return &MockK8sClients_Expecter{mock: &_m.Mock}
}
// AddStar provides a mock function with given fields: c, uid
func (_m *MockK8sClients) AddStar(c *contextmodel.ReqContext, uid string) error {
ret := _m.Called(c, uid)
if len(ret) == 0 {
panic("no return value specified for AddStar")
}
var r0 error
if rf, ok := ret.Get(0).(func(*contextmodel.ReqContext, string) error); ok {
r0 = rf(c, uid)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockK8sClients_AddStar_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddStar'
type MockK8sClients_AddStar_Call struct {
*mock.Call
}
// AddStar is a helper method to define mock.On call
// - c *contextmodel.ReqContext
// - uid string
func (_e *MockK8sClients_Expecter) AddStar(c interface{}, uid interface{}) *MockK8sClients_AddStar_Call {
return &MockK8sClients_AddStar_Call{Call: _e.mock.On("AddStar", c, uid)}
}
func (_c *MockK8sClients_AddStar_Call) Run(run func(c *contextmodel.ReqContext, uid string)) *MockK8sClients_AddStar_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*contextmodel.ReqContext), args[1].(string))
})
return _c
}
func (_c *MockK8sClients_AddStar_Call) Return(_a0 error) *MockK8sClients_AddStar_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockK8sClients_AddStar_Call) RunAndReturn(run func(*contextmodel.ReqContext, string) error) *MockK8sClients_AddStar_Call {
_c.Call.Return(run)
return _c
}
// GetDashboardID provides a mock function with given fields: c, uid
func (_m *MockK8sClients) GetDashboardID(c *contextmodel.ReqContext, uid string) (int64, response.Response) {
ret := _m.Called(c, uid)
if len(ret) == 0 {
panic("no return value specified for GetDashboardID")
}
var r0 int64
var r1 response.Response
if rf, ok := ret.Get(0).(func(*contextmodel.ReqContext, string) (int64, response.Response)); ok {
return rf(c, uid)
}
if rf, ok := ret.Get(0).(func(*contextmodel.ReqContext, string) int64); ok {
r0 = rf(c, uid)
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func(*contextmodel.ReqContext, string) response.Response); ok {
r1 = rf(c, uid)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(response.Response)
}
}
return r0, r1
}
// MockK8sClients_GetDashboardID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDashboardID'
type MockK8sClients_GetDashboardID_Call struct {
*mock.Call
}
// GetDashboardID is a helper method to define mock.On call
// - c *contextmodel.ReqContext
// - uid string
func (_e *MockK8sClients_Expecter) GetDashboardID(c interface{}, uid interface{}) *MockK8sClients_GetDashboardID_Call {
return &MockK8sClients_GetDashboardID_Call{Call: _e.mock.On("GetDashboardID", c, uid)}
}
func (_c *MockK8sClients_GetDashboardID_Call) Run(run func(c *contextmodel.ReqContext, uid string)) *MockK8sClients_GetDashboardID_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*contextmodel.ReqContext), args[1].(string))
})
return _c
}
func (_c *MockK8sClients_GetDashboardID_Call) Return(_a0 int64, _a1 response.Response) *MockK8sClients_GetDashboardID_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockK8sClients_GetDashboardID_Call) RunAndReturn(run func(*contextmodel.ReqContext, string) (int64, response.Response)) *MockK8sClients_GetDashboardID_Call {
_c.Call.Return(run)
return _c
}
// GetStars provides a mock function with given fields: c
func (_m *MockK8sClients) GetStars(c *contextmodel.ReqContext) ([]string, error) {
ret := _m.Called(c)
if len(ret) == 0 {
panic("no return value specified for GetStars")
}
var r0 []string
var r1 error
if rf, ok := ret.Get(0).(func(*contextmodel.ReqContext) ([]string, error)); ok {
return rf(c)
}
if rf, ok := ret.Get(0).(func(*contextmodel.ReqContext) []string); ok {
r0 = rf(c)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
if rf, ok := ret.Get(1).(func(*contextmodel.ReqContext) error); ok {
r1 = rf(c)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockK8sClients_GetStars_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetStars'
type MockK8sClients_GetStars_Call struct {
*mock.Call
}
// GetStars is a helper method to define mock.On call
// - c *contextmodel.ReqContext
func (_e *MockK8sClients_Expecter) GetStars(c interface{}) *MockK8sClients_GetStars_Call {
return &MockK8sClients_GetStars_Call{Call: _e.mock.On("GetStars", c)}
}
func (_c *MockK8sClients_GetStars_Call) Run(run func(c *contextmodel.ReqContext)) *MockK8sClients_GetStars_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*contextmodel.ReqContext))
})
return _c
}
func (_c *MockK8sClients_GetStars_Call) Return(_a0 []string, _a1 error) *MockK8sClients_GetStars_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockK8sClients_GetStars_Call) RunAndReturn(run func(*contextmodel.ReqContext) ([]string, error)) *MockK8sClients_GetStars_Call {
_c.Call.Return(run)
return _c
}
// RemoveStar provides a mock function with given fields: c, uid
func (_m *MockK8sClients) RemoveStar(c *contextmodel.ReqContext, uid string) error {
ret := _m.Called(c, uid)
if len(ret) == 0 {
panic("no return value specified for RemoveStar")
}
var r0 error
if rf, ok := ret.Get(0).(func(*contextmodel.ReqContext, string) error); ok {
r0 = rf(c, uid)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockK8sClients_RemoveStar_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveStar'
type MockK8sClients_RemoveStar_Call struct {
*mock.Call
}
// RemoveStar is a helper method to define mock.On call
// - c *contextmodel.ReqContext
// - uid string
func (_e *MockK8sClients_Expecter) RemoveStar(c interface{}, uid interface{}) *MockK8sClients_RemoveStar_Call {
return &MockK8sClients_RemoveStar_Call{Call: _e.mock.On("RemoveStar", c, uid)}
}
func (_c *MockK8sClients_RemoveStar_Call) Run(run func(c *contextmodel.ReqContext, uid string)) *MockK8sClients_RemoveStar_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*contextmodel.ReqContext), args[1].(string))
})
return _c
}
func (_c *MockK8sClients_RemoveStar_Call) Return(_a0 error) *MockK8sClients_RemoveStar_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockK8sClients_RemoveStar_Call) RunAndReturn(run func(*contextmodel.ReqContext, string) error) *MockK8sClients_RemoveStar_Call {
_c.Call.Return(run)
return _c
}
// NewMockK8sClients creates a new instance of MockK8sClients. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockK8sClients(t interface {
mock.TestingT
Cleanup(func())
}) *MockK8sClients {
mock := &MockK8sClients{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+11 -6
View File
@@ -26,14 +26,19 @@ func TestIntegrationStars(t *testing.T) {
for _, mode := range []grafanarest.DualWriterMode{
grafanarest.Mode0,
grafanarest.Mode2, // anything past 2 will fail
grafanarest.Mode2,
grafanarest.Mode3,
grafanarest.Mode5,
} {
flags := []string{featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs}
if mode > grafanarest.Mode2 {
flags = append(flags, featuremgmt.FlagKubernetesStars)
}
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: false, // required for experimental APIs
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
},
AppModeProduction: false, // required for experimental APIs
DisableAnonymous: true,
EnableFeatureToggles: flags,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: mode,