mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Alerting: In migration improve deduplication of title and group This change improves alert titles generated in the legacy migration that occur when we need to deduplicate titles. Now when duplicate titles are detected we will first attempt to append a sequential index, falling back to a random uid if none are unique within 10 attempts. This should cause shorter and more easily readable deduplicated titles in most cases. In addition, groups are no longer deduplicated. Instead we set them to a combination of truncated dashboard name and humanized alert frequency. This way, alerts from the same dashboard share a group if they have the same evaluation interval. In the event that truncation causes overlap, it won't be a big issue as all alerts will still be in a group with the correct evaluation interval.
822 lines
31 KiB
Go
822 lines
31 KiB
Go
package migration
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/alerting/models"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
|
ngModels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
"github.com/grafana/grafana/pkg/services/team"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
// TestDashAlertPermissionMigration tests the execution of the migration specifically for dashboards with custom permissions.
|
|
//
|
|
//nolint:gocyclo
|
|
func TestDashAlertPermissionMigration(t *testing.T) {
|
|
genLegacyAlert := func(name string, dashboardId int64, mutators ...func(*models.Alert)) *models.Alert {
|
|
a := createAlert(t, 1, int(dashboardId), 1, name, nil)
|
|
if len(mutators) > 0 {
|
|
for _, mutator := range mutators {
|
|
mutator(a)
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
genAlert := func(title string, namespaceUID string, dashboardUID string, mutators ...func(*ngModels.AlertRule)) *ngModels.AlertRule {
|
|
dashTitle := "Dashboard Title " + dashboardUID
|
|
a := &ngModels.AlertRule{
|
|
ID: 1,
|
|
OrgID: 1,
|
|
Title: title,
|
|
Condition: "A",
|
|
Data: []ngModels.AlertQuery{
|
|
{
|
|
RefID: "A",
|
|
DatasourceUID: "__expr__",
|
|
Model: json.RawMessage(`{"conditions":[],"intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"classic_conditions"}`),
|
|
},
|
|
},
|
|
NamespaceUID: namespaceUID,
|
|
DashboardUID: &dashboardUID,
|
|
RuleGroup: fmt.Sprintf("%s - 1m", dashTitle),
|
|
IntervalSeconds: 60,
|
|
Version: 1,
|
|
PanelID: pointer(int64(1)),
|
|
RuleGroupIndex: 1,
|
|
NoDataState: ngModels.NoData,
|
|
ExecErrState: ngModels.AlertingErrState,
|
|
For: 60 * time.Second,
|
|
Annotations: map[string]string{
|
|
"message": "message",
|
|
"__dashboardUid__": dashboardUID,
|
|
"__panelId__": "1",
|
|
},
|
|
Labels: map[string]string{},
|
|
IsPaused: false,
|
|
}
|
|
if len(mutators) > 0 {
|
|
for _, mutator := range mutators {
|
|
mutator(a)
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
withPanelId := func(id int64) func(*ngModels.AlertRule) {
|
|
return func(a *ngModels.AlertRule) {
|
|
a.PanelID = pointer(id)
|
|
a.Annotations["__panelId__"] = fmt.Sprintf("%d", id)
|
|
}
|
|
}
|
|
|
|
genFolder := func(t *testing.T, id int64, uid string, mutators ...func(f *dashboards.Dashboard)) *dashboards.Dashboard {
|
|
d := createFolder(t, id, 1, uid)
|
|
d.Title = "Original Folder " + uid
|
|
if len(mutators) > 0 {
|
|
for _, mutator := range mutators {
|
|
mutator(d)
|
|
}
|
|
}
|
|
return d
|
|
}
|
|
|
|
genCreatedFolder := func(t *testing.T, title string, mutators ...func(f *dashboards.Dashboard)) *dashboards.Dashboard {
|
|
d := createFolder(t, 1, 1, "") // Leave generated UID blank, so we don't compare.
|
|
d.Title = title
|
|
d.CreatedBy = -1
|
|
d.UpdatedBy = -1
|
|
if len(mutators) > 0 {
|
|
for _, mutator := range mutators {
|
|
mutator(d)
|
|
}
|
|
}
|
|
return d
|
|
}
|
|
|
|
genDashboard := func(t *testing.T, id int64, uid string, folderId int64, mutators ...func(f *dashboards.Dashboard)) *dashboards.Dashboard {
|
|
d := createDashboard(t, id, 1, uid, folderId, nil)
|
|
d.Title = "Dashboard Title " + uid
|
|
if len(mutators) > 0 {
|
|
for _, mutator := range mutators {
|
|
mutator(d)
|
|
}
|
|
}
|
|
return d
|
|
}
|
|
|
|
genPerms := func(perms ...accesscontrol.SetResourcePermissionCommand) []accesscontrol.SetResourcePermissionCommand {
|
|
return perms
|
|
}
|
|
|
|
type expectedAlertMigration struct {
|
|
Alert *ngModels.AlertRule
|
|
Folder *dashboards.Dashboard
|
|
Perms []accesscontrol.SetResourcePermissionCommand
|
|
}
|
|
|
|
type testcase struct {
|
|
name string
|
|
enterprise bool
|
|
folders []*dashboards.Dashboard
|
|
folderPerms map[string][]accesscontrol.SetResourcePermissionCommand // UID -> Perms
|
|
dashboards []*dashboards.Dashboard
|
|
dashboardPerms map[string][]accesscontrol.SetResourcePermissionCommand // UID -> Perms
|
|
alerts []*models.Alert
|
|
roles map[accesscontrol.Role][]accesscontrol.Permission
|
|
|
|
expected []expectedAlertMigration
|
|
}
|
|
|
|
// Used to perform the same tests for each of builtins, users, and teams.
|
|
splitTestcase := func(raw testcase) []testcase {
|
|
permTypes := make(map[string]func(accesscontrol.SetResourcePermissionCommand) accesscontrol.SetResourcePermissionCommand, 3)
|
|
permTypes["builtins"] = func(p accesscontrol.SetResourcePermissionCommand) accesscontrol.SetResourcePermissionCommand {
|
|
return p
|
|
}
|
|
mapping := map[string]int64{
|
|
string(org.RoleEditor): 1,
|
|
string(org.RoleViewer): 2,
|
|
}
|
|
permTypes["users"] = func(p accesscontrol.SetResourcePermissionCommand) accesscontrol.SetResourcePermissionCommand {
|
|
id, ok := mapping[p.BuiltinRole]
|
|
if !ok {
|
|
return p
|
|
}
|
|
p.UserID = id
|
|
p.BuiltinRole = ""
|
|
return p
|
|
}
|
|
permTypes["teams"] = func(p accesscontrol.SetResourcePermissionCommand) accesscontrol.SetResourcePermissionCommand {
|
|
id, ok := mapping[p.BuiltinRole]
|
|
if !ok {
|
|
return p
|
|
}
|
|
p.TeamID = id
|
|
p.BuiltinRole = ""
|
|
return p
|
|
}
|
|
|
|
applyTransform := func(tt testcase, pfunc func(p accesscontrol.SetResourcePermissionCommand) accesscontrol.SetResourcePermissionCommand) testcase {
|
|
folderPerms := make(map[string][]accesscontrol.SetResourcePermissionCommand, len(tt.folderPerms))
|
|
for _, f := range tt.folders {
|
|
perms := make([]accesscontrol.SetResourcePermissionCommand, 0, len(tt.folderPerms[f.UID]))
|
|
for _, p := range tt.folderPerms[f.UID] {
|
|
perms = append(perms, pfunc(p))
|
|
}
|
|
folderPerms[f.UID] = perms
|
|
}
|
|
tt.folderPerms = folderPerms
|
|
|
|
dashboardPerms := make(map[string][]accesscontrol.SetResourcePermissionCommand, len(tt.dashboardPerms))
|
|
for _, d := range tt.dashboards {
|
|
perms := make([]accesscontrol.SetResourcePermissionCommand, 0, len(tt.dashboardPerms[d.UID]))
|
|
for _, p := range tt.dashboardPerms[d.UID] {
|
|
perms = append(perms, pfunc(p))
|
|
}
|
|
dashboardPerms[d.UID] = perms
|
|
}
|
|
tt.dashboardPerms = dashboardPerms
|
|
|
|
expected := make([]expectedAlertMigration, 0, len(tt.expected))
|
|
for _, ex := range tt.expected {
|
|
permissions := make([]accesscontrol.SetResourcePermissionCommand, 0, len(ex.Perms))
|
|
for _, p := range ex.Perms {
|
|
permissions = append(permissions, pfunc(p))
|
|
}
|
|
ex.Perms = permissions
|
|
|
|
sort.SliceStable(permissions, func(i, j int) bool {
|
|
if permissions[i].BuiltinRole != permissions[j].BuiltinRole {
|
|
return permissions[i].BuiltinRole < permissions[j].BuiltinRole
|
|
}
|
|
if permissions[i].UserID != permissions[j].UserID {
|
|
return permissions[i].UserID < permissions[j].UserID
|
|
}
|
|
if permissions[i].TeamID != permissions[j].TeamID {
|
|
return permissions[i].TeamID < permissions[j].TeamID
|
|
}
|
|
return permissions[i].Permission < permissions[j].Permission
|
|
})
|
|
|
|
f := *ex.Folder
|
|
if strings.Contains(f.Title, "%s") {
|
|
hash, err := createHash(permissions)
|
|
require.NoError(t, err)
|
|
f.Title = fmt.Sprintf(f.Title, hash)
|
|
}
|
|
|
|
expected = append(expected, expectedAlertMigration{
|
|
Alert: ex.Alert,
|
|
Folder: &f,
|
|
Perms: permissions,
|
|
})
|
|
}
|
|
tt.expected = expected
|
|
|
|
return tt
|
|
}
|
|
|
|
cases := make([]testcase, 0, 3)
|
|
for k, pfunc := range permTypes {
|
|
tt := applyTransform(raw, pfunc)
|
|
tt.name = k
|
|
cases = append(cases, tt)
|
|
}
|
|
return cases
|
|
}
|
|
|
|
basicFolder := genFolder(t, 1, "f_1")
|
|
basicDashboard := genDashboard(t, 2, "d_1", basicFolder.ID)
|
|
defaultPerms := genPerms(
|
|
accesscontrol.SetResourcePermissionCommand{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
accesscontrol.SetResourcePermissionCommand{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
)
|
|
|
|
basicAlert1 := genLegacyAlert("alert1", basicDashboard.ID, func(a *models.Alert) { a.PanelID = 1 })
|
|
basicAlert2 := genLegacyAlert("alert2", basicDashboard.ID, func(a *models.Alert) { a.PanelID = 2 })
|
|
|
|
basicPerms := func() map[accesscontrol.Role][]accesscontrol.Permission {
|
|
basic := make(map[accesscontrol.Role][]accesscontrol.Permission)
|
|
var permissions []accesscontrol.Permission
|
|
ts := time.Now()
|
|
for _, action := range append(ossaccesscontrol.DashboardAdminActions, ossaccesscontrol.FolderAdminActions...) {
|
|
if isDashboardAction := strings.HasPrefix(action, "dashboards"); isDashboardAction {
|
|
permissions = append(permissions, accesscontrol.Permission{
|
|
Action: action,
|
|
Scope: dashboards.ScopeDashboardsAll,
|
|
Created: ts,
|
|
Updated: ts,
|
|
})
|
|
}
|
|
permissions = append(permissions, accesscontrol.Permission{
|
|
Action: action,
|
|
Scope: dashboards.ScopeFoldersAll,
|
|
Created: ts,
|
|
Updated: ts,
|
|
})
|
|
}
|
|
basic[accesscontrol.Role{Name: accesscontrol.BasicRolePrefix + "admin"}] = permissions
|
|
return basic
|
|
}
|
|
|
|
tc := []testcase{
|
|
{
|
|
name: "alerts in dashboard and folder with default permissions migrate to same folder",
|
|
folders: []*dashboards.Dashboard{basicFolder},
|
|
folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicFolder.UID: defaultPerms},
|
|
dashboards: []*dashboards.Dashboard{basicDashboard},
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicDashboard.UID: defaultPerms},
|
|
alerts: []*models.Alert{basicAlert1, basicAlert2},
|
|
expected: []expectedAlertMigration{
|
|
{
|
|
Alert: genAlert(basicAlert1.Name, basicFolder.UID, basicDashboard.UID, withPanelId(basicAlert1.PanelID)),
|
|
Folder: basicFolder,
|
|
Perms: defaultPerms,
|
|
},
|
|
{
|
|
Alert: genAlert(basicAlert2.Name, basicFolder.UID, basicDashboard.UID, withPanelId(basicAlert2.PanelID)),
|
|
Folder: basicFolder,
|
|
Perms: defaultPerms,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "dashboard override cannot lessen folder permissions",
|
|
folders: []*dashboards.Dashboard{basicFolder},
|
|
folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicFolder.UID: defaultPerms},
|
|
dashboards: []*dashboards.Dashboard{basicDashboard},
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
|
|
basicDashboard.UID: {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_VIEW.String()}, // Change.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
},
|
|
},
|
|
alerts: []*models.Alert{basicAlert1},
|
|
expected: []expectedAlertMigration{
|
|
{
|
|
Alert: genAlert(basicAlert1.Name, basicFolder.UID, basicDashboard.UID),
|
|
Folder: basicFolder,
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Inherits from Folder.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "dashboard with various permission overrides should create new folder",
|
|
folders: []*dashboards.Dashboard{basicFolder},
|
|
folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicFolder.UID: defaultPerms},
|
|
dashboards: []*dashboards.Dashboard{
|
|
genDashboard(t, 2, "d_1", basicFolder.ID),
|
|
genDashboard(t, 3, "d_2", basicFolder.ID),
|
|
genDashboard(t, 4, "d_3", basicFolder.ID),
|
|
genDashboard(t, 5, "d_4", basicFolder.ID),
|
|
genDashboard(t, 6, "d_5", basicFolder.ID),
|
|
},
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
|
|
"d_1": {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
|
|
},
|
|
"d_2": {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
|
|
},
|
|
"d_3": {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
|
|
},
|
|
"d_4": {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
},
|
|
"d_5": {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
|
|
},
|
|
},
|
|
alerts: []*models.Alert{genLegacyAlert("alert1", 2), genLegacyAlert("alert2", 3), genLegacyAlert("alert3", 4), genLegacyAlert("alert4", 5), genLegacyAlert("alert5", 6)},
|
|
expected: []expectedAlertMigration{
|
|
{
|
|
Alert: genAlert("alert1", "", "d_1"),
|
|
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
|
|
},
|
|
},
|
|
{
|
|
Alert: genAlert("alert2", "", "d_2"),
|
|
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
|
|
},
|
|
},
|
|
{
|
|
Alert: genAlert("alert3", "", "d_3"),
|
|
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
|
|
},
|
|
},
|
|
{
|
|
Alert: genAlert("alert4", "", "d_4"),
|
|
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
},
|
|
},
|
|
{
|
|
Alert: genAlert("alert5", "", "d_5"),
|
|
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Change.
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "missing dashboard permission is inherited from folder",
|
|
folders: []*dashboards.Dashboard{genFolder(t, 1, "f_1"), genFolder(t, 2, "f_2")},
|
|
folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
|
|
"f_1": {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()},
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()},
|
|
},
|
|
"f_2": {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
},
|
|
},
|
|
dashboards: []*dashboards.Dashboard{
|
|
genDashboard(t, 3, "d_1", 1),
|
|
genDashboard(t, 4, "d_2", 1),
|
|
genDashboard(t, 5, "d_3", 2),
|
|
genDashboard(t, 6, "d_4", 2),
|
|
},
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
|
|
"d_1": {
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
},
|
|
"d_2": {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
},
|
|
"d_3": {
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
},
|
|
"d_4": {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
},
|
|
},
|
|
alerts: []*models.Alert{genLegacyAlert("alert1", 3), genLegacyAlert("alert2", 4), genLegacyAlert("alert3", 5), genLegacyAlert("alert4", 6)},
|
|
expected: []expectedAlertMigration{
|
|
{
|
|
Alert: genAlert("alert1", "f_1", "d_1"),
|
|
Folder: genFolder(t, 1, "f_1"), // Original folder since the perms didn't change.
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Inherits from Folder.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Overrides from Folder.
|
|
},
|
|
},
|
|
{
|
|
Alert: genAlert("alert2", "f_1", "d_2"),
|
|
Folder: genFolder(t, 1, "f_1"), // Original folder since the perms didn't change.
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Overrides from Folder.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // Inherits from Folder.
|
|
},
|
|
},
|
|
{
|
|
Alert: genAlert("alert3", "f_2", "d_3"),
|
|
Folder: genFolder(t, 2, "f_2"), // Original folder since the perms didn't change.
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_VIEW.String()}, // Inherits from Folder.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
},
|
|
},
|
|
{
|
|
Alert: genAlert("alert4", "", "d_4"),
|
|
Folder: genCreatedFolder(t, "Original Folder f_2 Alerts - %s"),
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, // Inherits from Folder.
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "missing dashboard and folder view permission is still missing",
|
|
folders: []*dashboards.Dashboard{basicFolder},
|
|
folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
|
|
basicFolder.UID: {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
},
|
|
},
|
|
dashboards: []*dashboards.Dashboard{basicDashboard},
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
|
|
basicDashboard.UID: {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
|
},
|
|
},
|
|
alerts: []*models.Alert{basicAlert1},
|
|
expected: []expectedAlertMigration{
|
|
{
|
|
Alert: genAlert(basicAlert1.Name, basicFolder.UID, basicDashboard.UID),
|
|
Folder: basicFolder,
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// General folder.
|
|
{
|
|
name: "dashboard in general folder with default permissions migrates to General Alerting subfolder for permission",
|
|
dashboards: []*dashboards.Dashboard{genDashboard(t, 1, "d_1", 0)}, // Dashboard in general folder.
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
|
|
"d_1": defaultPerms,
|
|
},
|
|
alerts: []*models.Alert{genLegacyAlert("alert1", 1)},
|
|
expected: []expectedAlertMigration{
|
|
{
|
|
Alert: genAlert("alert1", "f_1", "d_1"),
|
|
Folder: genCreatedFolder(t, "General Alerting Alerts - %s"),
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // From Dashboard.
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, // From Dashboard.
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "dashboard in general folder with some perms migrates to General Alerting subfolder with correct permissions",
|
|
dashboards: []*dashboards.Dashboard{genDashboard(t, 1, "d_1", 0)}, // Dashboard in general folder.
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
|
|
"d_1": { // Missing viewer.
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
},
|
|
},
|
|
alerts: []*models.Alert{genLegacyAlert("alert1", 1)},
|
|
expected: []expectedAlertMigration{
|
|
{
|
|
Alert: genAlert("alert1", "f_1", "d_1"),
|
|
Folder: genCreatedFolder(t, "General Alerting Alerts - %s"),
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // From Dashboard.
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "dashboard in general folder with empty perms migrates to General Alerting",
|
|
dashboards: []*dashboards.Dashboard{genDashboard(t, 1, "d_1", 0)}, // Dashboard in general folder.
|
|
alerts: []*models.Alert{genLegacyAlert("alert1", 1)},
|
|
expected: []expectedAlertMigration{
|
|
{
|
|
Alert: genAlert("alert1", "f_1", "d_1"),
|
|
Folder: genCreatedFolder(t, "General Alerting"),
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{},
|
|
},
|
|
},
|
|
},
|
|
|
|
// The following tests handled extra requirements of enterprise RBAC in that they include basic, fixed, and custom roles.
|
|
{
|
|
name: "should handle basic roles the same as managed builtin roles",
|
|
enterprise: true,
|
|
roles: basicPerms(),
|
|
folders: []*dashboards.Dashboard{basicFolder},
|
|
folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicFolder.UID: defaultPerms},
|
|
dashboards: []*dashboards.Dashboard{
|
|
genDashboard(t, 2, "d_1", basicFolder.ID),
|
|
},
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
|
|
"d_1": {
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
|
|
},
|
|
},
|
|
alerts: []*models.Alert{genLegacyAlert("alert1", 2)},
|
|
expected: []expectedAlertMigration{
|
|
{
|
|
Alert: genAlert("alert1", "", "d_1"),
|
|
Folder: genCreatedFolder(t, "Original Folder f_1 Alerts - %s"),
|
|
Perms: []accesscontrol.SetResourcePermissionCommand{
|
|
{BuiltinRole: string(org.RoleAdmin), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, // From basic:admin.
|
|
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
|
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_EDIT.String()}, // Change.
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "should ignore fixed roles even if they would affect access",
|
|
enterprise: true,
|
|
roles: map[accesscontrol.Role][]accesscontrol.Permission{
|
|
{Name: "fixed:dashboards:writer"}: {
|
|
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeDashboardsAll},
|
|
{Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeDashboardsAll},
|
|
{Action: dashboards.ActionDashboardsDelete, Scope: dashboards.ScopeDashboardsAll},
|
|
{Action: dashboards.ActionDashboardsCreate, Scope: dashboards.ScopeFoldersAll},
|
|
{Action: dashboards.ActionDashboardsPermissionsRead, Scope: dashboards.ScopeDashboardsAll},
|
|
{Action: dashboards.ActionDashboardsPermissionsWrite, Scope: dashboards.ScopeDashboardsAll},
|
|
},
|
|
},
|
|
folders: []*dashboards.Dashboard{basicFolder},
|
|
folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicFolder.UID: defaultPerms},
|
|
dashboards: []*dashboards.Dashboard{basicDashboard},
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicDashboard.UID: defaultPerms},
|
|
alerts: []*models.Alert{basicAlert1},
|
|
expected: []expectedAlertMigration{ // Expect no new folder.
|
|
{
|
|
Alert: genAlert(basicAlert1.Name, basicFolder.UID, basicDashboard.UID),
|
|
Folder: basicFolder,
|
|
Perms: defaultPerms,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "should ignore custom roles even if they would affect access",
|
|
enterprise: true,
|
|
roles: map[accesscontrol.Role][]accesscontrol.Permission{
|
|
{Name: "custom role"}: {
|
|
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeDashboardsAll},
|
|
{Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeDashboardsAll},
|
|
{Action: dashboards.ActionDashboardsDelete, Scope: dashboards.ScopeDashboardsAll},
|
|
{Action: dashboards.ActionDashboardsCreate, Scope: dashboards.ScopeFoldersAll},
|
|
{Action: dashboards.ActionDashboardsPermissionsRead, Scope: dashboards.ScopeDashboardsAll},
|
|
{Action: dashboards.ActionDashboardsPermissionsWrite, Scope: dashboards.ScopeDashboardsAll},
|
|
},
|
|
},
|
|
folders: []*dashboards.Dashboard{basicFolder},
|
|
folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicFolder.UID: defaultPerms},
|
|
dashboards: []*dashboards.Dashboard{basicDashboard},
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicDashboard.UID: defaultPerms},
|
|
alerts: []*models.Alert{basicAlert1},
|
|
expected: []expectedAlertMigration{ // Expect no new folder.
|
|
{
|
|
Alert: genAlert(basicAlert1.Name, basicFolder.UID, basicDashboard.UID),
|
|
Folder: basicFolder,
|
|
Perms: defaultPerms,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, ttRaw := range tc {
|
|
t.Run(ttRaw.name, func(t *testing.T) {
|
|
for _, tt := range splitTestcase(ttRaw) {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sqlStore := db.InitTestDB(t)
|
|
x := sqlStore.GetEngine()
|
|
|
|
if tt.enterprise {
|
|
createRoles(t, context.Background(), sqlStore, tt.roles)
|
|
}
|
|
|
|
service := NewTestMigrationService(t, sqlStore, &setting.Cfg{})
|
|
setupLegacyAlertsTables(t, x, nil, tt.alerts, tt.folders, tt.dashboards)
|
|
|
|
for i := 1; i < 3; i++ {
|
|
_, err := x.Insert(user.User{
|
|
ID: int64(i),
|
|
OrgID: 1,
|
|
Name: fmt.Sprintf("user%v", i),
|
|
Login: fmt.Sprintf("user%v", i),
|
|
Email: fmt.Sprintf("user%v@example.org", i),
|
|
Created: now,
|
|
Updated: now,
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
for i := 1; i < 3; i++ {
|
|
_, err := x.Insert(team.Team{
|
|
ID: int64(i),
|
|
OrgID: 1,
|
|
UID: fmt.Sprintf("team%v", i),
|
|
Name: fmt.Sprintf("team%v", i),
|
|
Created: now,
|
|
Updated: now,
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
for _, f := range tt.folders {
|
|
_, err := service.migrationStore.SetFolderPermissions(context.Background(), 1, f.UID, tt.folderPerms[f.UID]...)
|
|
require.NoError(t, err)
|
|
}
|
|
for _, d := range tt.dashboards {
|
|
_, err := service.migrationStore.SetDashboardPermissions(context.Background(), 1, d.UID, tt.dashboardPerms[d.UID]...)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
err := service.Run(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// construct actuals.
|
|
orgId := int64(1)
|
|
rules := getAlertRules(t, x, orgId)
|
|
actual := make([]expectedAlertMigration, 0, len(rules))
|
|
for i, r := range rules {
|
|
// Remove generated fields.
|
|
require.NotEqual(t, r.Labels["rule_uid"], "")
|
|
delete(r.Labels, "rule_uid")
|
|
require.NotEqual(t, r.Annotations["__alertId__"], "")
|
|
delete(r.Annotations, "__alertId__")
|
|
|
|
folder := getDashboard(t, x, orgId, r.NamespaceUID)
|
|
rperms, err := service.migrationStore.GetFolderPermissions(context.Background(), getMigrationUser(orgId), folder.UID)
|
|
require.NoError(t, err)
|
|
|
|
expected := tt.expected[i]
|
|
if expected.Folder.UID == "" {
|
|
// We're expecting the UID to be generated, so remove it from comparison.
|
|
folder.UID = ""
|
|
r.NamespaceUID = ""
|
|
expected.Alert.NamespaceUID = ""
|
|
}
|
|
|
|
keep := make(map[accesscontrol.SetResourcePermissionCommand]dashboardaccess.PermissionType)
|
|
for _, p := range rperms {
|
|
if permission := service.migrationStore.MapActions(p); permission != "" {
|
|
sp := accesscontrol.SetResourcePermissionCommand{
|
|
UserID: p.UserId,
|
|
TeamID: p.TeamId,
|
|
BuiltinRole: p.BuiltInRole,
|
|
}
|
|
pType := permissionMap[permission]
|
|
current, ok := keep[sp]
|
|
if !ok || pType > current {
|
|
keep[sp] = pType
|
|
}
|
|
}
|
|
}
|
|
perms := make([]accesscontrol.SetResourcePermissionCommand, 0, len(keep))
|
|
for p, pType := range keep {
|
|
p.Permission = pType.String()
|
|
perms = append(perms, p)
|
|
}
|
|
|
|
actual = append(actual, expectedAlertMigration{
|
|
Alert: r,
|
|
Folder: folder,
|
|
Perms: perms,
|
|
})
|
|
}
|
|
|
|
cOpt := []cmp.Option{
|
|
cmpopts.SortSlices(func(a, b expectedAlertMigration) bool {
|
|
return a.Alert.Title < b.Alert.Title
|
|
}),
|
|
cmpopts.SortSlices(func(a, b accesscontrol.SetResourcePermissionCommand) bool {
|
|
if a.BuiltinRole != b.BuiltinRole {
|
|
return a.BuiltinRole < b.BuiltinRole
|
|
}
|
|
if a.UserID != b.UserID {
|
|
return a.UserID < b.UserID
|
|
}
|
|
if a.TeamID != b.TeamID {
|
|
return a.TeamID < b.TeamID
|
|
}
|
|
return a.Permission < b.Permission
|
|
}),
|
|
cmpopts.IgnoreUnexported(ngModels.AlertRule{}, ngModels.AlertQuery{}),
|
|
cmpopts.IgnoreFields(ngModels.AlertRule{}, "ID", "Updated", "UID"),
|
|
cmpopts.IgnoreFields(dashboards.Dashboard{}, "ID", "Created", "Updated", "Data", "Slug"),
|
|
}
|
|
if !cmp.Equal(tt.expected, actual, cOpt...) {
|
|
t.Errorf("Unexpected Rule: %v", cmp.Diff(tt.expected, actual, cOpt...))
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func createRoles(t testing.TB, ctx context.Context, store db.DB, rolePerms map[accesscontrol.Role][]accesscontrol.Permission) {
|
|
_ = store.WithDbSession(ctx, func(sess *db.Session) error {
|
|
ts := time.Now()
|
|
var roles []accesscontrol.Role
|
|
|
|
basic := accesscontrol.BuildBasicRoleDefinitions()
|
|
|
|
var permissions []accesscontrol.Permission
|
|
var builtinRoleAssignments []accesscontrol.BuiltinRole
|
|
var userRoleAssignments []accesscontrol.UserRole
|
|
var teamRoleAssignments []accesscontrol.TeamRole
|
|
i := int64(1)
|
|
for role, perms := range rolePerms {
|
|
if role.IsBasic() {
|
|
for roleType, br := range basic {
|
|
if br.Name == role.Name {
|
|
role = br.Role()
|
|
builtinRoleAssignments = append(builtinRoleAssignments, accesscontrol.BuiltinRole{
|
|
OrgID: accesscontrol.GlobalOrgID, RoleID: i, Role: roleType, Created: ts, Updated: ts,
|
|
})
|
|
}
|
|
}
|
|
} else {
|
|
userRoleAssignments = append(userRoleAssignments, accesscontrol.UserRole{
|
|
OrgID: accesscontrol.GlobalOrgID, RoleID: i, UserID: 1, Created: ts,
|
|
})
|
|
teamRoleAssignments = append(teamRoleAssignments, accesscontrol.TeamRole{
|
|
OrgID: accesscontrol.GlobalOrgID, RoleID: i, TeamID: 1, Created: ts,
|
|
})
|
|
}
|
|
role.ID = i
|
|
role.Created = ts
|
|
role.Updated = ts
|
|
|
|
roles = append(roles, role)
|
|
|
|
for _, p := range perms {
|
|
permissions = append(permissions, accesscontrol.Permission{
|
|
RoleID: role.ID, Action: p.Action, Scope: p.Scope, Created: ts, Updated: ts,
|
|
})
|
|
}
|
|
i++
|
|
}
|
|
|
|
_, err := sess.InsertMulti(&roles)
|
|
require.NoError(t, err)
|
|
|
|
_, err = sess.InsertMulti(&permissions)
|
|
require.NoError(t, err)
|
|
|
|
_, err = sess.InsertMulti(&builtinRoleAssignments)
|
|
require.NoError(t, err)
|
|
|
|
_, err = sess.InsertMulti(&userRoleAssignments)
|
|
require.NoError(t, err)
|
|
|
|
_, err = sess.InsertMulti(&teamRoleAssignments)
|
|
require.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
}
|