mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s: Provisioned dashboard support (#98843)
This commit is contained in:
committed by
GitHub
parent
d8d84a000a
commit
9ec10be1c7
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -135,7 +136,8 @@ func TestDashboardService(t *testing.T) {
|
||||
dto := &dashboards.SaveDashboardDTO{}
|
||||
|
||||
t.Run("Should not return validation error if dashboard is provisioned", func(t *testing.T) {
|
||||
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand"), mock.AnythingOfType("*dashboards.DashboardProvisioning")).Return(&dashboards.Dashboard{Data: simplejson.New()}, nil).Once()
|
||||
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.AnythingOfType("*dashboards.DashboardProvisioning")).Return(nil).Once()
|
||||
fakeStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{Data: simplejson.New()}, nil).Once()
|
||||
|
||||
dto.Dashboard = dashboards.NewDashboard("Dash")
|
||||
dto.Dashboard.SetID(3)
|
||||
@@ -145,7 +147,8 @@ func TestDashboardService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Should override invalid refresh interval if dashboard is provisioned", func(t *testing.T) {
|
||||
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand"), mock.AnythingOfType("*dashboards.DashboardProvisioning")).Return(&dashboards.Dashboard{Data: simplejson.New()}, nil).Once()
|
||||
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.AnythingOfType("*dashboards.DashboardProvisioning")).Return(nil).Once()
|
||||
fakeStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{Data: simplejson.New()}, nil).Once()
|
||||
|
||||
oldRefreshInterval := service.cfg.MinRefreshInterval
|
||||
service.cfg.MinRefreshInterval = "5m"
|
||||
@@ -246,7 +249,10 @@ func (m *mockDashK8sCli) getClient(ctx context.Context, orgID int64) (dynamic.Re
|
||||
}
|
||||
|
||||
func (m *mockDashK8sCli) getNamespace(orgID int64) string {
|
||||
return "default"
|
||||
if orgID == 1 {
|
||||
return "default"
|
||||
}
|
||||
return fmt.Sprintf("orgs-%d", orgID)
|
||||
}
|
||||
|
||||
func (m *mockDashK8sCli) getSearcher() resource.ResourceIndexClient {
|
||||
@@ -567,6 +573,617 @@ func TestGetAllDashboardsByOrgId(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetProvisionedDashboardData(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
orgService: &orgtest.FakeOrgService{
|
||||
ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
fakeStore.On("GetProvisionedDashboardData", mock.Anything, "test").Return([]*dashboards.DashboardProvisioning{}, nil).Once()
|
||||
dashboard, err := service.GetProvisionedDashboardData(context.Background(), "test")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dashboard)
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled and get from relevant org", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, mock.Anything).Return(k8sResourceMock, true)
|
||||
k8sResourceMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"labels": map[string]any{
|
||||
utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "test",
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
},
|
||||
}}, nil).Once()
|
||||
repo := "test"
|
||||
k8sClientMock.searcher.On("Search",
|
||||
mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
// ensure the prefix is added to the query
|
||||
return req.Options.Key.Namespace == "default" && req.Options.Fields[0].Values[0] == provisionedFileNameWithPrefix(repo)
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{},
|
||||
Rows: []*resource.ResourceTableRow{},
|
||||
},
|
||||
TotalHits: 0,
|
||||
}, nil).Once()
|
||||
k8sClientMock.searcher.On("Search", mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
// ensure the prefix is added to the query
|
||||
return req.Options.Key.Namespace == "orgs-2" && req.Options.Fields[0].Values[0] == provisionedFileNameWithPrefix(repo)
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Dashboard 1"),
|
||||
[]byte("folder 1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TotalHits: 1,
|
||||
}, nil).Once()
|
||||
dashes, err := service.GetProvisionedDashboardData(ctx, repo)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dashes, 1)
|
||||
require.Equal(t, dashes[0], &dashboards.DashboardProvisioning{
|
||||
ID: 0,
|
||||
DashboardID: 1,
|
||||
Name: "test",
|
||||
ExternalID: "path/to/file",
|
||||
CheckSum: "hash",
|
||||
Updated: 1735689600,
|
||||
})
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
orgService: &orgtest.FakeOrgService{
|
||||
ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, int64(1)).Return(&dashboards.DashboardProvisioning{}, nil).Once()
|
||||
dashboard, err := service.GetProvisionedDashboardDataByDashboardID(context.Background(), 1)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dashboard)
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled and get from whatever org it is in", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, mock.Anything).Return(k8sResourceMock, true)
|
||||
k8sResourceMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"labels": map[string]any{
|
||||
utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "test",
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
},
|
||||
}}, nil)
|
||||
k8sClientMock.searcher.On("Search", mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Key.Namespace == "default"
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{},
|
||||
Rows: []*resource.ResourceTableRow{},
|
||||
},
|
||||
TotalHits: 0,
|
||||
}, nil)
|
||||
k8sClientMock.searcher.On("Search", mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Key.Namespace == "orgs-2"
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Dashboard 1"),
|
||||
[]byte("folder 1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TotalHits: 1,
|
||||
}, nil)
|
||||
dash, err := service.GetProvisionedDashboardDataByDashboardID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dash, &dashboards.DashboardProvisioning{
|
||||
ID: 0,
|
||||
DashboardID: 1,
|
||||
Name: "test",
|
||||
ExternalID: "path/to/file",
|
||||
CheckSum: "hash",
|
||||
Updated: 1735689600,
|
||||
})
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
orgService: &orgtest.FakeOrgService{
|
||||
ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
fakeStore.On("GetProvisionedDataByDashboardUID", mock.Anything, int64(1), "test").Return(&dashboards.DashboardProvisioning{}, nil).Once()
|
||||
dashboard, err := service.GetProvisionedDashboardDataByDashboardUID(context.Background(), 1, "test")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dashboard)
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, mock.Anything).Return(k8sResourceMock, true)
|
||||
k8sResourceMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"labels": map[string]any{
|
||||
utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "test",
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
},
|
||||
}}, nil).Once()
|
||||
k8sClientMock.searcher.On("Search", mock.Anything).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Dashboard 1"),
|
||||
[]byte("folder 1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TotalHits: 1,
|
||||
}, nil).Once()
|
||||
dash, err := service.GetProvisionedDashboardDataByDashboardUID(ctx, 1, "uid")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dash, &dashboards.DashboardProvisioning{
|
||||
ID: 0,
|
||||
DashboardID: 1,
|
||||
Name: "test",
|
||||
ExternalID: "path/to/file",
|
||||
CheckSum: "hash",
|
||||
Updated: 1735689600,
|
||||
})
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
orgService: &orgtest.FakeOrgService{
|
||||
ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
fakeStore.On("DeleteOrphanedProvisionedDashboards", mock.Anything, &dashboards.DeleteOrphanedProvisionedDashboardsCommand{
|
||||
ReaderNames: []string{"test"},
|
||||
}).Return(nil).Once()
|
||||
err := service.DeleteOrphanedProvisionedDashboards(context.Background(), &dashboards.DeleteOrphanedProvisionedDashboardsCommand{
|
||||
ReaderNames: []string{"test"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled, delete across all orgs, but only delete file based provisioned dashboards", func(t *testing.T) {
|
||||
_, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, mock.Anything).Return(k8sResourceMock, true)
|
||||
k8sResourceMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{UID: "uid", OrgID: 1}).Return(nil).Once()
|
||||
fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{UID: "uid3", OrgID: 2}).Return(nil).Once()
|
||||
k8sResourceMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "orphaned",
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{},
|
||||
}}, nil).Once()
|
||||
// should not delete this one, because it does not start with "file:"
|
||||
k8sResourceMock.On("Get", mock.Anything, "uid2", mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid2",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: "plugin",
|
||||
utils.AnnoKeyRepoHash: "app",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{},
|
||||
}}, nil).Once()
|
||||
|
||||
k8sResourceMock.On("Get", mock.Anything, "uid3", mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid3",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "orphaned",
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{},
|
||||
}}, nil).Once()
|
||||
k8sClientMock.searcher.On("Search", mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Key.Namespace == "default" && req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == provisionedFileNameWithPrefix("test") &&
|
||||
req.Options.Fields[0].Operator == "notin"
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Dashboard 1"),
|
||||
[]byte("folder 1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TotalHits: 1,
|
||||
}, nil).Once()
|
||||
|
||||
k8sClientMock.searcher.On("Search", mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Key.Namespace == "orgs-2" && req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == provisionedFileNameWithPrefix("test") &&
|
||||
req.Options.Fields[0].Operator == "notin"
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid2",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Dashboard 2"),
|
||||
[]byte("folder 2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid3",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Dashboard 3"),
|
||||
[]byte("folder 3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TotalHits: 2,
|
||||
}, nil).Once()
|
||||
err := service.DeleteOrphanedProvisionedDashboards(context.Background(), &dashboards.DeleteOrphanedProvisionedDashboardsCommand{
|
||||
ReaderNames: []string{"test"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnprovisionDashboard(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
orgService: &orgtest.FakeOrgService{
|
||||
ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
fakeStore.On("UnprovisionDashboard", mock.Anything, int64(1)).Return(nil).Once()
|
||||
err := service.UnprovisionDashboard(context.Background(), 1)
|
||||
require.NoError(t, err)
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled - should remove annotations", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, mock.Anything).Return(k8sResourceMock, true)
|
||||
dash := &unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "test",
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{},
|
||||
}}
|
||||
k8sResourceMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(dash, nil)
|
||||
dashWithoutAnnotations := &unstructured.Unstructured{Object: map[string]any{
|
||||
"apiVersion": "dashboard.grafana.app/v0alpha1",
|
||||
"kind": "Dashboard",
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"namespace": "default",
|
||||
"annotations": map[string]any{},
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"uid": "uid",
|
||||
"version": 1,
|
||||
},
|
||||
}}
|
||||
// should update it to be without annotations
|
||||
k8sResourceMock.On("Update", mock.Anything, dashWithoutAnnotations, mock.Anything, mock.Anything).Return(dashWithoutAnnotations, nil)
|
||||
k8sClientMock.searcher.On("Search", mock.Anything).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Dashboard 1"),
|
||||
[]byte("folder 1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TotalHits: 1,
|
||||
}, nil)
|
||||
err := service.UnprovisionDashboard(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetDashboardsByPluginID(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
}
|
||||
|
||||
query := &dashboards.GetDashboardsByPluginIDQuery{
|
||||
PluginID: "testing",
|
||||
OrgID: 1,
|
||||
}
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
fakeStore.On("GetDashboardsByPluginID", mock.Anything, mock.Anything).Return([]*dashboards.Dashboard{}, nil).Once()
|
||||
_, err := service.GetDashboardsByPluginID(context.Background(), query)
|
||||
require.NoError(t, err)
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sClientMock, _ := setupK8sDashboardTests(service)
|
||||
k8sClientMock.searcher.On("Search", mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == "plugin" &&
|
||||
req.Options.Fields[1].Key == "repo.path" && req.Options.Fields[1].Values[0] == "testing"
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Dashboard 1"),
|
||||
[]byte("folder 1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TotalHits: 1,
|
||||
}, nil)
|
||||
dashes, err := service.GetDashboardsByPluginID(ctx, query)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dashes, 1)
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSaveProvisionedDashboard(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
log: log.NewNopLogger(),
|
||||
}
|
||||
|
||||
origNewDashboardGuardian := guardian.New
|
||||
defer func() { guardian.New = origNewDashboardGuardian }()
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
|
||||
|
||||
query := &dashboards.SaveDashboardDTO{
|
||||
OrgID: 1,
|
||||
User: &user.SignedInUser{UserID: 1},
|
||||
Dashboard: &dashboards.Dashboard{
|
||||
UID: "uid",
|
||||
Title: "testing slugify",
|
||||
Slug: "testing-slugify",
|
||||
OrgID: 1,
|
||||
Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid"}),
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
|
||||
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
fakeStore.On("SaveDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
|
||||
dashboard, err := service.SaveProvisionedDashboard(context.Background(), query, &dashboards.DashboardProvisioning{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dashboard)
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
},
|
||||
}}
|
||||
|
||||
t.Run("Should use Kubernetes create if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true)
|
||||
k8sResourceMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||
k8sResourceMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil)
|
||||
|
||||
dashboard, err := service.SaveProvisionedDashboard(ctx, query, &dashboards.DashboardProvisioning{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dashboard)
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
// ensure the provisioning data is still saved to the db
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSaveDashboard(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
@@ -667,22 +1284,19 @@ func TestDeleteDashboard(t *testing.T) {
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true).Once()
|
||||
k8sResourceMock.On("Delete", mock.Anything, mock.Anything, metav1.DeleteOptions{
|
||||
GracePeriodSeconds: nil,
|
||||
}, mock.Anything).Return(nil).Once()
|
||||
k8sResourceMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
|
||||
err := service.DeleteDashboard(ctx, 1, "uid", 1)
|
||||
require.NoError(t, err)
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("When UID is not passed in for provisioned dashboards, should retrieve that first and set grace period to zero", func(t *testing.T) {
|
||||
t.Run("If UID is not passed in, it should retrieve that first", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
zeroInt64 := int64(0)
|
||||
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true).Once()
|
||||
k8sResourceMock.On("Delete", mock.Anything, mock.Anything, metav1.DeleteOptions{
|
||||
GracePeriodSeconds: &zeroInt64,
|
||||
}, mock.Anything).Return(nil).Once()
|
||||
k8sResourceMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
k8sClientMock.searcher.On("Search", mock.Anything).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -708,7 +1322,7 @@ func TestDeleteDashboard(t *testing.T) {
|
||||
},
|
||||
TotalHits: 1,
|
||||
}, nil)
|
||||
err := service.DeleteProvisionedDashboard(ctx, 1, 1)
|
||||
err := service.DeleteDashboard(ctx, 1, "", 1)
|
||||
require.NoError(t, err)
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
k8sClientMock.searcher.AssertExpectations(t)
|
||||
|
||||
Reference in New Issue
Block a user