Files
grafana/pkg/services/ngalert/store/namespace.go
T
Alexander Akhmetov c7c68322b1 Alerting: Allow specifying a folder for Prometheus rule import (#101406)
What is this feature?

Allows the creation of alert rules with mimirtool in a specified folder.

Why do we need this feature?

Currently, the APIs for mimirtool create namespaces and rule groups in the root folder without the ability to set a custom folder. For example, it could be a special "Imported" folder, etc.

This PR makes it possible with a special header: mimirtool ... --extra-headers="X-Grafana-Alerting-Folder-UID=123". If it's not present, the root folder is used, otherwise, the specified one is used.

mimirtool does not support nested folder structures, while Grafana allows folder nesting. To keep compatibility, we return only direct child folders of the working folder (as namespaces) with rule groups and rules that are directly in these child folders as if there are no nested folders.

For example, given this folder structure in Grafana:

```
	grafana/
	├── production/
	│   ├── service1/
	│   │   └── alerts/
	│   └── service2/
	└── testing/
	    └── service3/
```

If the working folder is "grafana":

    Only namespaces "production" and "testing" are returned
    Only rule groups directly within these folders are included

If the working folder is "production":
   -  Only namespaces "service1" and "service2" are returned
    Only rule groups directly within these folders are included
2025-03-03 17:59:01 +01:00

116 lines
3.3 KiB
Go

package store
import (
"context"
"errors"
"sort"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
)
// GetUserVisibleNamespaces returns the folders that are visible to the user
func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user identity.Requester) (map[string]*folder.Folder, error) {
folders, err := st.FolderService.GetFolders(ctx, folder.GetFoldersQuery{
OrgID: orgID,
WithFullpath: true,
SignedInUser: user,
})
if err != nil {
return nil, err
}
namespaceMap := make(map[string]*folder.Folder)
for _, f := range folders {
namespaceMap[f.UID] = f
}
return namespaceMap, nil
}
// GetNamespaceByUID is a handler for retrieving a namespace by its UID. Alerting rules follow a Grafana folder-like structure which we call namespaces.
func (st DBstore) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error) {
f, err := st.FolderService.GetFolders(ctx, folder.GetFoldersQuery{OrgID: orgID, UIDs: []string{uid}, WithFullpath: true, SignedInUser: user})
if err != nil {
return nil, err
}
if len(f) == 0 {
return nil, dashboards.ErrFolderAccessDenied
}
return f[0], nil
}
// GetNamespaceChildren gets namespace (folder) children (first level) by its UID.
func (st DBstore) GetNamespaceChildren(ctx context.Context, uid string, orgID int64, user identity.Requester) ([]*folder.Folder, error) {
q := &folder.GetChildrenQuery{
UID: uid,
OrgID: orgID,
SignedInUser: user,
}
folders, err := st.FolderService.GetChildren(ctx, q)
if err != nil {
return nil, err
}
found := make([]*folder.Folder, 0, len(folders))
for _, f := range folders {
if f.ParentUID == uid {
found = append(found, f)
}
}
return found, nil
}
// GetNamespaceByTitle gets namespace by its title in the specified folder.
func (st DBstore) GetNamespaceByTitle(ctx context.Context, title string, orgID int64, user identity.Requester, parentUID string) (*folder.Folder, error) {
folders, err := st.GetNamespaceChildren(ctx, parentUID, orgID, user)
if err != nil {
return nil, err
}
foundByTitle := []*folder.Folder{}
for _, f := range folders {
if f.Title == title {
foundByTitle = append(foundByTitle, f)
}
}
if len(foundByTitle) == 0 {
return nil, dashboards.ErrFolderNotFound
}
// Sort by UID to return the first folder in case of multiple folders with the same title
sort.Slice(foundByTitle, func(i, j int) bool {
return foundByTitle[i].UID < foundByTitle[j].UID
})
return foundByTitle[0], nil
}
// GetOrCreateNamespaceByTitle gets or creates a namespace by title in the specified folder.
func (st DBstore) GetOrCreateNamespaceByTitle(ctx context.Context, title string, orgID int64, user identity.Requester, parentUID string) (*folder.Folder, error) {
var f *folder.Folder
var err error
f, err = st.GetNamespaceByTitle(ctx, title, orgID, user, parentUID)
if err != nil && !errors.Is(err, dashboards.ErrFolderNotFound) {
return nil, err
}
if f == nil {
cmd := &folder.CreateFolderCommand{
OrgID: orgID,
Title: title,
SignedInUser: user,
ParentUID: parentUID,
}
f, err = st.FolderService.Create(ctx, cmd)
if err != nil {
return nil, err
}
}
return f, nil
}