K8s/Annotations: Use manager/source annotations rather than repo (#101313)
Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
This commit is contained in:
co-authored by
Stephanie Hingtgen
parent
e7baf9804e
commit
dc2defd84f
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/infra/slugify"
|
||||
@@ -438,9 +439,10 @@ type FindPersistedDashboardsQuery struct {
|
||||
Sort model.SortOption
|
||||
IsDeleted bool
|
||||
|
||||
ProvisionedRepo string
|
||||
ProvisionedPath string
|
||||
ProvisionedReposNotIn []string
|
||||
ManagedBy utils.ManagerKind
|
||||
ManagerIdentity string
|
||||
SourcePath string
|
||||
ManagerIdentityNotIn []string
|
||||
|
||||
Filters []any
|
||||
|
||||
|
||||
@@ -237,7 +237,8 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context,
|
||||
func(orgID int64) {
|
||||
g.Go(func() error {
|
||||
res, err := dr.searchProvisionedDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
|
||||
ProvisionedRepo: name,
|
||||
ManagedBy: utils.ManagerKindClassicFP, // nolint:staticcheck
|
||||
ManagerIdentity: name,
|
||||
OrgId: orgID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -568,8 +569,8 @@ func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.
|
||||
ctx, _ := identity.WithServiceIdentity(ctx, org.ID)
|
||||
// find all dashboards in the org that have a file repo set that is not in the given readers list
|
||||
foundDashs, err := dr.searchProvisionedDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
|
||||
ProvisionedReposNotIn: cmd.ReaderNames,
|
||||
OrgId: org.ID,
|
||||
ManagerIdentityNotIn: cmd.ReaderNames,
|
||||
OrgId: org.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -962,8 +963,8 @@ func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, que
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
|
||||
OrgId: query.OrgID,
|
||||
ProvisionedRepo: dashboard.PluginIDRepoName,
|
||||
ProvisionedPath: query.PluginID,
|
||||
ManagedBy: utils.ManagerKindPlugin,
|
||||
ManagerIdentity: query.PluginID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1553,22 +1554,22 @@ func (dr *DashboardServiceImpl) saveProvisionedDashboardThroughK8s(ctx context.C
|
||||
return nil, err
|
||||
}
|
||||
|
||||
annotations := obj.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
meta, err := utils.MetaAccessor(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if unprovision {
|
||||
delete(annotations, utils.AnnoKeyRepoName)
|
||||
delete(annotations, utils.AnnoKeyRepoPath)
|
||||
delete(annotations, utils.AnnoKeyRepoHash)
|
||||
delete(annotations, utils.AnnoKeyRepoTimestamp)
|
||||
} else {
|
||||
annotations[utils.AnnoKeyRepoName] = dashboard.ProvisionedFileNameWithPrefix(provisioning.Name)
|
||||
annotations[utils.AnnoKeyRepoPath] = provisioning.ExternalID
|
||||
annotations[utils.AnnoKeyRepoHash] = provisioning.CheckSum
|
||||
annotations[utils.AnnoKeyRepoTimestamp] = time.Unix(provisioning.Updated, 0).UTC().Format(time.RFC3339)
|
||||
|
||||
m := utils.ManagerProperties{}
|
||||
s := utils.SourceProperties{}
|
||||
if !unprovision {
|
||||
m.Kind = utils.ManagerKindClassicFP // nolint:staticcheck
|
||||
m.Identity = provisioning.Name
|
||||
s.Path = provisioning.ExternalID
|
||||
s.Checksum = provisioning.CheckSum
|
||||
s.TimestampMillis = time.Unix(provisioning.Updated, 0).UnixMilli()
|
||||
}
|
||||
obj.SetAnnotations(annotations)
|
||||
meta.SetManagerProperties(m)
|
||||
meta.SetSourceProperties(s)
|
||||
|
||||
out, err := dr.createOrUpdateDash(ctx, obj, cmd.OrgID)
|
||||
if err != nil {
|
||||
@@ -1594,16 +1595,16 @@ func (dr *DashboardServiceImpl) saveDashboardThroughK8s(ctx context.Context, cmd
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) createOrUpdateDash(ctx context.Context, obj unstructured.Unstructured, orgID int64) (*dashboards.Dashboard, error) {
|
||||
func (dr *DashboardServiceImpl) createOrUpdateDash(ctx context.Context, obj *unstructured.Unstructured, orgID int64) (*dashboards.Dashboard, error) {
|
||||
var out *unstructured.Unstructured
|
||||
current, err := dr.k8sclient.Get(ctx, obj.GetName(), orgID, v1.GetOptions{})
|
||||
if current == nil || err != nil {
|
||||
out, err = dr.k8sclient.Create(ctx, &obj, orgID)
|
||||
out, err = dr.k8sclient.Create(ctx, obj, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
out, err = dr.k8sclient.Update(ctx, &obj, orgID)
|
||||
out, err = dr.k8sclient.Update(ctx, obj, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1723,30 +1724,35 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
|
||||
})
|
||||
}
|
||||
|
||||
if query.ProvisionedRepo != "" {
|
||||
req := []*resource.Requirement{{
|
||||
Key: resource.SEARCH_FIELD_REPOSITORY_NAME,
|
||||
Operator: string(selection.In),
|
||||
Values: []string{query.ProvisionedRepo},
|
||||
}}
|
||||
request.Options.Fields = append(request.Options.Fields, req...)
|
||||
if query.ManagedBy != "" {
|
||||
request.Options.Fields = append(request.Options.Fields, &resource.Requirement{
|
||||
Key: resource.SEARCH_FIELD_MANAGER_KIND,
|
||||
Operator: string(selection.Equals),
|
||||
Values: []string{string(query.ManagedBy)},
|
||||
})
|
||||
}
|
||||
|
||||
if len(query.ProvisionedReposNotIn) > 0 {
|
||||
req := []*resource.Requirement{{
|
||||
Key: resource.SEARCH_FIELD_REPOSITORY_NAME,
|
||||
Operator: string(selection.NotIn),
|
||||
Values: query.ProvisionedReposNotIn,
|
||||
}}
|
||||
request.Options.Fields = append(request.Options.Fields, req...)
|
||||
}
|
||||
if query.ProvisionedPath != "" {
|
||||
req := []*resource.Requirement{{
|
||||
Key: resource.SEARCH_FIELD_REPOSITORY_PATH,
|
||||
if query.ManagerIdentity != "" {
|
||||
request.Options.Fields = append(request.Options.Fields, &resource.Requirement{
|
||||
Key: resource.SEARCH_FIELD_MANAGER_ID,
|
||||
Operator: string(selection.In),
|
||||
Values: []string{query.ProvisionedPath},
|
||||
}}
|
||||
request.Options.Fields = append(request.Options.Fields, req...)
|
||||
Values: []string{query.ManagerIdentity},
|
||||
})
|
||||
}
|
||||
|
||||
if len(query.ManagerIdentityNotIn) > 0 {
|
||||
request.Options.Fields = append(request.Options.Fields, &resource.Requirement{
|
||||
Key: resource.SEARCH_FIELD_MANAGER_ID,
|
||||
Operator: string(selection.NotIn),
|
||||
Values: query.ManagerIdentityNotIn,
|
||||
})
|
||||
}
|
||||
if query.SourcePath != "" {
|
||||
request.Options.Fields = append(request.Options.Fields, &resource.Requirement{
|
||||
Key: resource.SEARCH_FIELD_SOURCE_PATH,
|
||||
Operator: string(selection.In),
|
||||
Values: []string{query.SourcePath},
|
||||
})
|
||||
}
|
||||
|
||||
if query.Title != "" {
|
||||
@@ -1840,18 +1846,6 @@ func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx contex
|
||||
|
||||
ctx, _ = identity.WithServiceIdentity(ctx, query.OrgId)
|
||||
|
||||
if query.ProvisionedRepo != "" {
|
||||
query.ProvisionedRepo = dashboard.ProvisionedFileNameWithPrefix(query.ProvisionedRepo)
|
||||
}
|
||||
|
||||
if len(query.ProvisionedReposNotIn) > 0 {
|
||||
repos := make([]string, len(query.ProvisionedReposNotIn))
|
||||
for i, v := range query.ProvisionedReposNotIn {
|
||||
repos[i] = dashboard.ProvisionedFileNameWithPrefix(v)
|
||||
}
|
||||
query.ProvisionedReposNotIn = repos
|
||||
}
|
||||
|
||||
query.Type = searchstore.TypeDashboard
|
||||
|
||||
searchResults, err := dr.searchDashboardsThroughK8sRaw(ctx, query)
|
||||
@@ -1878,26 +1872,27 @@ func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx contex
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure the repo is set due to file provisioning, otherwise skip it
|
||||
fileRepo, found := dashboard.GetProvisionedFileNameFromMeta(meta.GetRepositoryName())
|
||||
if !found {
|
||||
m, ok := meta.GetManagerProperties()
|
||||
if !ok || m.Kind != utils.ManagerKindClassicFP { // nolint:staticcheck
|
||||
return nil
|
||||
}
|
||||
|
||||
source, ok := meta.GetSourceProperties()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
provisioning := &dashboardProvisioningWithUID{
|
||||
DashboardProvisioning: dashboards.DashboardProvisioning{
|
||||
Name: m.Identity,
|
||||
ExternalID: source.Path,
|
||||
CheckSum: source.Checksum,
|
||||
DashboardID: meta.GetDeprecatedInternalID(), // nolint:staticcheck
|
||||
},
|
||||
DashboardUID: hit.Name,
|
||||
}
|
||||
provisioning.Name = fileRepo
|
||||
provisioning.ExternalID = meta.GetRepositoryPath()
|
||||
provisioning.CheckSum = meta.GetRepositoryHash()
|
||||
provisioning.DashboardID = meta.GetDeprecatedInternalID() // nolint:staticcheck
|
||||
|
||||
updated, err := meta.GetRepositoryTimestamp()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if updated != nil {
|
||||
provisioning.Updated = updated.Unix()
|
||||
if source.TimestampMillis > 0 {
|
||||
provisioning.Updated = time.UnixMilli(source.TimestampMillis).Unix()
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
@@ -2027,13 +2022,13 @@ func (dr *DashboardServiceImpl) UnstructuredToLegacyDashboard(ctx context.Contex
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func LegacySaveCommandToUnstructured(cmd *dashboards.SaveDashboardCommand, namespace string) (unstructured.Unstructured, error) {
|
||||
func LegacySaveCommandToUnstructured(cmd *dashboards.SaveDashboardCommand, namespace string) (*unstructured.Unstructured, error) {
|
||||
uid := cmd.GetDashboardModel().UID
|
||||
if uid == "" {
|
||||
uid = uuid.NewString()
|
||||
}
|
||||
|
||||
finalObj := unstructured.Unstructured{
|
||||
finalObj := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{},
|
||||
}
|
||||
|
||||
@@ -2061,7 +2056,7 @@ func LegacySaveCommandToUnstructured(cmd *dashboards.SaveDashboardCommand, names
|
||||
finalObj.SetNamespace(namespace)
|
||||
finalObj.SetGroupVersionKind(dashboardv0alpha1.DashboardResourceInfo.GroupVersionKind())
|
||||
|
||||
meta, err := utils.MetaAccessor(&finalObj)
|
||||
meta, err := utils.MetaAccessor(finalObj)
|
||||
if err != nil {
|
||||
return finalObj, err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -9,13 +10,12 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard"
|
||||
dashboardv0alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
@@ -540,31 +540,38 @@ func TestGetProvisionedDashboardData(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled and get from relevant org", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
provisioningTimestamp := int64(1234567)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Get", mock.Anything, 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
|
||||
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": dashboardv0alpha1.DashboardResourceInfo.GroupVersion().String(),
|
||||
"kind": dashboardv0alpha1.DashboardResourceInfo.GroupVersionKind().Kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "uid",
|
||||
"labels": map[string]interface{}{
|
||||
utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck
|
||||
utils.AnnoKeyManagerIdentity: "test",
|
||||
utils.AnnoKeySourceChecksum: "hash",
|
||||
utils.AnnoKeySourcePath: "path/to/file",
|
||||
utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()),
|
||||
},
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("test"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
"spec": map[string]interface{}{
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
},
|
||||
}}, nil).Once()
|
||||
}, nil).Once()
|
||||
repo := "test"
|
||||
k8sCliMock.On("Search", mock.Anything, int64(1),
|
||||
mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
// ensure the prefix is added to the query
|
||||
return req.Options.Fields[0].Values[0] == dashboard.ProvisionedFileNameWithPrefix(repo)
|
||||
// make sure the kind is added to the query
|
||||
return req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && // nolint:staticcheck
|
||||
req.Options.Fields[1].Values[0] == repo
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{},
|
||||
@@ -573,8 +580,9 @@ func TestGetProvisionedDashboardData(t *testing.T) {
|
||||
TotalHits: 0,
|
||||
}, nil).Once()
|
||||
k8sCliMock.On("Search", mock.Anything, int64(2), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
// ensure the prefix is added to the query
|
||||
return req.Options.Fields[0].Values[0] == dashboard.ProvisionedFileNameWithPrefix(repo)
|
||||
// make sure the kind is added to the query
|
||||
return req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && // nolint:staticcheck
|
||||
req.Options.Fields[1].Values[0] == repo
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -611,7 +619,7 @@ func TestGetProvisionedDashboardData(t *testing.T) {
|
||||
Name: "test",
|
||||
ExternalID: "path/to/file",
|
||||
CheckSum: "hash",
|
||||
Updated: 1735689600,
|
||||
Updated: provisioningTimestamp,
|
||||
})
|
||||
k8sCliMock.AssertExpectations(t)
|
||||
})
|
||||
@@ -639,21 +647,25 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.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, k8sCliMock := setupK8sDashboardTests(service)
|
||||
provisioningTimestamp := int64(1234567)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": dashboardv0alpha1.DashboardResourceInfo.GroupVersion().String(),
|
||||
"kind": dashboardv0alpha1.DashboardResourceInfo.GroupVersionKind().Kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "uid",
|
||||
"labels": map[string]any{
|
||||
"labels": map[string]interface{}{
|
||||
utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("test"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
"annotations": map[string]interface{}{
|
||||
utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck
|
||||
utils.AnnoKeyManagerIdentity: "test",
|
||||
utils.AnnoKeySourceChecksum: "hash",
|
||||
utils.AnnoKeySourcePath: "path/to/file",
|
||||
utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()),
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"spec": map[string]interface{}{
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
@@ -701,7 +713,7 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) {
|
||||
Name: "test",
|
||||
ExternalID: "path/to/file",
|
||||
CheckSum: "hash",
|
||||
Updated: 1735689600,
|
||||
Updated: provisioningTimestamp,
|
||||
})
|
||||
k8sCliMock.AssertExpectations(t)
|
||||
})
|
||||
@@ -729,21 +741,25 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
provisioningTimestamp := int64(1234567)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": dashboardv0alpha1.DashboardResourceInfo.GroupVersion().String(),
|
||||
"kind": dashboardv0alpha1.DashboardResourceInfo.GroupVersionKind().Kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "uid",
|
||||
"labels": map[string]any{
|
||||
"labels": map[string]interface{}{
|
||||
utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("test"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
"annotations": map[string]interface{}{
|
||||
utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck
|
||||
utils.AnnoKeyManagerIdentity: "test",
|
||||
utils.AnnoKeySourceChecksum: "hash",
|
||||
utils.AnnoKeySourcePath: "path/to/file",
|
||||
utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()),
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"spec": map[string]interface{}{
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
@@ -784,7 +800,7 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) {
|
||||
Name: "test",
|
||||
ExternalID: "path/to/file",
|
||||
CheckSum: "hash",
|
||||
Updated: 1735689600,
|
||||
Updated: provisioningTimestamp,
|
||||
})
|
||||
k8sCliMock.AssertExpectations(t)
|
||||
})
|
||||
@@ -826,10 +842,11 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("orphaned"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck
|
||||
utils.AnnoKeyManagerIdentity: "orphaned",
|
||||
utils.AnnoKeySourceChecksum: "hash",
|
||||
utils.AnnoKeySourcePath: "path/to/file",
|
||||
utils.AnnoKeySourceTimestamp: "2025-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{},
|
||||
@@ -839,8 +856,8 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
"metadata": map[string]any{
|
||||
"name": "uid2",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: dashboard.PluginIDRepoName,
|
||||
utils.AnnoKeyRepoHash: "app",
|
||||
utils.AnnoKeyManagerKind: string(utils.ManagerKindPlugin),
|
||||
utils.AnnoKeyManagerIdentity: "app",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{},
|
||||
@@ -850,16 +867,17 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
"metadata": map[string]any{
|
||||
"name": "uid3",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("orphaned"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck
|
||||
utils.AnnoKeyManagerIdentity: "orphaned",
|
||||
utils.AnnoKeySourceChecksum: "hash",
|
||||
utils.AnnoKeySourcePath: "path/to/file",
|
||||
utils.AnnoKeySourceTimestamp: "2025-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{},
|
||||
}}, nil).Once()
|
||||
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == dashboard.ProvisionedFileNameWithPrefix("test") && req.Options.Fields[0].Operator == "notin"
|
||||
return req.Options.Fields[0].Key == "manager.id" && req.Options.Fields[0].Values[0] == "test" && req.Options.Fields[0].Operator == "notin"
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -889,7 +907,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
}, nil).Once()
|
||||
|
||||
k8sCliMock.On("Search", mock.Anything, int64(2), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == dashboard.ProvisionedFileNameWithPrefix("test") && req.Options.Fields[0].Operator == "notin"
|
||||
return req.Options.Fields[0].Key == "manager.id" && req.Options.Fields[0].Values[0] == "test" && req.Options.Fields[0].Operator == "notin"
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -960,10 +978,11 @@ func TestUnprovisionDashboard(t *testing.T) {
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("test"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
utils.AnnoKeyManagerKind: utils.ManagerKindClassicFP, // nolint:staticcheck
|
||||
utils.AnnoKeyManagerIdentity: "test",
|
||||
utils.AnnoKeySourceChecksum: "hash",
|
||||
utils.AnnoKeySourcePath: "path/to/file",
|
||||
utils.AnnoKeySourceTimestamp: "2025-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{},
|
||||
@@ -983,7 +1002,7 @@ func TestUnprovisionDashboard(t *testing.T) {
|
||||
},
|
||||
}}
|
||||
// should update it to be without annotations
|
||||
k8sCliMock.On("Update", mock.Anything, dashWithoutAnnotations, mock.Anything, mock.Anything).Return(dashWithoutAnnotations, nil)
|
||||
k8sCliMock.On("Update", mock.Anything, dashWithoutAnnotations, mock.Anything).Return(dashWithoutAnnotations, nil)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything).Return("default")
|
||||
k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil)
|
||||
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
|
||||
@@ -1054,8 +1073,9 @@ func TestGetDashboardsByPluginID(t *testing.T) {
|
||||
k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(uidUnstructured, nil)
|
||||
k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil)
|
||||
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == dashboard.PluginIDRepoName &&
|
||||
req.Options.Fields[1].Key == "repo.path" && req.Options.Fields[1].Values[0] == "testing"
|
||||
return ( // gofmt comment helper
|
||||
req.Options.Fields[0].Key == "manager.kind" && req.Options.Fields[0].Values[0] == string(utils.ManagerKindPlugin) &&
|
||||
req.Options.Fields[1].Key == "manager.id" && req.Options.Fields[1].Values[0] == "testing")
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -1989,14 +2009,16 @@ func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) {
|
||||
query := &dashboards.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
}
|
||||
provisioningTimestamp := int64(1234567)
|
||||
dashboardUnstructuredProvisioned := unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("test"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck
|
||||
utils.AnnoKeyManagerIdentity: "test",
|
||||
utils.AnnoKeySourceChecksum: "hash",
|
||||
utils.AnnoKeySourcePath: "path/to/file",
|
||||
utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()),
|
||||
},
|
||||
},
|
||||
"spec": map[string]any{},
|
||||
@@ -2056,7 +2078,7 @@ func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) {
|
||||
Name: "test",
|
||||
ExternalID: "path/to/file",
|
||||
CheckSum: "hash",
|
||||
Updated: 1735689600,
|
||||
Updated: provisioningTimestamp,
|
||||
},
|
||||
},
|
||||
}, res) // only should return the one provisioned dashboard
|
||||
|
||||
@@ -29,10 +29,11 @@ var (
|
||||
resource.SEARCH_FIELD_CREATED_BY,
|
||||
resource.SEARCH_FIELD_UPDATED,
|
||||
resource.SEARCH_FIELD_UPDATED_BY,
|
||||
resource.SEARCH_FIELD_REPOSITORY_NAME,
|
||||
resource.SEARCH_FIELD_REPOSITORY_PATH,
|
||||
resource.SEARCH_FIELD_REPOSITORY_HASH,
|
||||
resource.SEARCH_FIELD_REPOSITORY_TIME,
|
||||
resource.SEARCH_FIELD_MANAGER_KIND,
|
||||
resource.SEARCH_FIELD_MANAGER_ID,
|
||||
resource.SEARCH_FIELD_SOURCE_PATH,
|
||||
resource.SEARCH_FIELD_SOURCE_CHECKSUM,
|
||||
resource.SEARCH_FIELD_SOURCE_TIME,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/infra/slugify"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func (ss *FolderUnifiedStoreImpl) UnstructuredToLegacyFolder(ctx context.Context, item *unstructured.Unstructured) (*folder.Folder, error) {
|
||||
@@ -57,7 +58,7 @@ func (ss *FolderUnifiedStoreImpl) UnstructuredToLegacyFolder(ctx context.Context
|
||||
if updater.UID == "" {
|
||||
updater = creator
|
||||
}
|
||||
|
||||
manager, _ := meta.GetManagerProperties()
|
||||
return &folder.Folder{
|
||||
UID: uid,
|
||||
Title: title,
|
||||
@@ -65,7 +66,7 @@ func (ss *FolderUnifiedStoreImpl) UnstructuredToLegacyFolder(ctx context.Context
|
||||
ID: meta.GetDeprecatedInternalID(), // nolint:staticcheck
|
||||
ParentUID: meta.GetFolder(),
|
||||
Version: int(meta.GetGeneration()),
|
||||
Repository: meta.GetRepositoryName(),
|
||||
ManagedBy: manager.Kind,
|
||||
|
||||
URL: url,
|
||||
Created: created,
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||
@@ -63,7 +64,7 @@ func TestFolderConversions(t *testing.T) {
|
||||
Title: "test folder",
|
||||
Description: "Something set in the file",
|
||||
URL: "/dashboards/f/be79sztagf20wd/test-folder",
|
||||
Repository: "example-repo",
|
||||
ManagedBy: utils.ManagerKindRepo,
|
||||
Created: created,
|
||||
Updated: created.Add(time.Hour * 5),
|
||||
CreatedBy: 10,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/infra/slugify"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
@@ -56,10 +57,10 @@ type Folder struct {
|
||||
Fullpath string `xorm:"fullpath"`
|
||||
FullpathUIDs string `xorm:"fullpath_uids"`
|
||||
|
||||
// When the folder belongs to a repository
|
||||
// The folder is managed by an external process
|
||||
// NOTE: this is only populated when folders are managed by unified storage
|
||||
// This is not ever used by xorm, but the translation functions flow through this type
|
||||
Repository string `json:"repository,omitempty"`
|
||||
ManagedBy utils.ManagerKind `json:"managedBy,omitempty"`
|
||||
}
|
||||
|
||||
var GeneralFolder = Folder{ID: 0, Title: "General"}
|
||||
|
||||
Reference in New Issue
Block a user