c7c68322b1
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
116 lines
3.3 KiB
Go
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
|
|
}
|