mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* In migration, create one label per channel This PR changes how routing is done by the legacy alerting migration. Previously, we created a single label on each alert rule that contained an array of contact point names. Ex: __contact__="slack legacy testing","slack legacy testing2" This label was then routed against a series of regex-matching policies with continue=true. Ex: __contacts__ =~ .*"slack legacy testing".* In the case of many contact points, this array could quickly become difficult to manage and difficult to grok at-a-glance. This PR replaces the single __contact__ label with multiple __legacy_c_{contactname}__ labels and simple equality-matching policies. These channel-specific policies are nested in a single route under the top-level route which matches against __legacy_use_channels__ = true for ease of organization. This should improve the experience for users wanting to keep the default migrated routing strategy but who also want to modify which contact points an alert sends to.
822 lines
32 KiB
Go
822 lines
32 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{ngModels.MigratedUseLegacyChannelsLabel: "true"},
|
|
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[ngModels.MigratedAlertIdAnnotation], "")
|
|
delete(r.Annotations, ngModels.MigratedAlertIdAnnotation)
|
|
|
|
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
|
|
})
|
|
}
|