Team/User: UID migrations (#82298)

* Add user uid migration to run on every startup to protect against empty values in a upgrade downgrade scenario

* Add team uid migration to run on every startup to protect against empty values in a upgrade downgrade scenario

* Run team uid migration
This commit is contained in:
Karl Persson
2024-02-12 14:48:29 +01:00
committed by GitHub
parent 685e84b1f8
commit 1315c67c8b
12 changed files with 84 additions and 16 deletions

View File

@@ -207,7 +207,8 @@ func setupDB(b testing.TB) benchScenario {
quotaService := quotatest.New(false, nil) quotaService := quotatest.New(false, nil)
cfg := setting.NewCfg() cfg := setting.NewCfg()
teamSvc := teamimpl.ProvideService(db, cfg) teamSvc, err := teamimpl.ProvideService(db, cfg)
require.NoError(b, err)
orgService, err := orgimpl.ProvideService(db, cfg, quotaService) orgService, err := orgimpl.ProvideService(db, cfg, quotaService)
require.NoError(b, err) require.NoError(b, err)

View File

@@ -613,9 +613,10 @@ func TestIntegrationMergeUser(t *testing.T) {
t.Run("should be able to merge user", func(t *testing.T) { t.Run("should be able to merge user", func(t *testing.T) {
// Restore after destructive operation // Restore after destructive operation
sqlStore := db.InitTestDB(t) sqlStore := db.InitTestDB(t)
teamSvc := teamimpl.ProvideService(sqlStore, setting.NewCfg()) teamSvc, err := teamimpl.ProvideService(sqlStore, setting.NewCfg())
require.NoError(t, err)
team1, err := teamSvc.CreateTeam("team1 name", "", 1) team1, err := teamSvc.CreateTeam("team1 name", "", 1)
require.Nil(t, err) require.NoError(t, err)
usrSvc := setupTestUserService(t, sqlStore) usrSvc := setupTestUserService(t, sqlStore)
const testOrgID int64 = 1 const testOrgID int64 = 1

View File

@@ -322,7 +322,8 @@ func setupTestEnv(t testing.TB) (*AccessControlStore, rs.Store, user.Service, te
cfg.AutoAssignOrgId = 1 cfg.AutoAssignOrgId = 1
acstore := ProvideService(sql) acstore := ProvideService(sql)
permissionStore := rs.NewStore(sql, featuremgmt.WithFeatures()) permissionStore := rs.NewStore(sql, featuremgmt.WithFeatures())
teamService := teamimpl.ProvideService(sql, cfg) teamService, err := teamimpl.ProvideService(sql, cfg)
require.NoError(t, err)
orgService, err := orgimpl.ProvideService(sql, cfg, quotatest.New(false, nil)) orgService, err := orgimpl.ProvideService(sql, cfg, quotatest.New(false, nil))
require.NoError(t, err) require.NoError(t, err)

View File

@@ -508,7 +508,8 @@ func checkSeededPermissions(t *testing.T, permissions []resourcePermissionDTO) {
func seedPermissions(t *testing.T, resourceID string, sql *sqlstore.SQLStore, service *Service) { func seedPermissions(t *testing.T, resourceID string, sql *sqlstore.SQLStore, service *Service) {
t.Helper() t.Helper()
// seed team 1 with "Edit" permission on dashboard 1 // seed team 1 with "Edit" permission on dashboard 1
teamSvc := teamimpl.ProvideService(sql, sql.Cfg) teamSvc, err := teamimpl.ProvideService(sql, sql.Cfg)
require.NoError(t, err)
team, err := teamSvc.CreateTeam("test", "test@test.com", 1) team, err := teamSvc.CreateTeam("test", "test@test.com", 1)
require.NoError(t, err) require.NoError(t, err)
_, err = service.SetTeamPermission(context.Background(), team.OrgID, team.ID, resourceID, "Edit") _, err = service.SetTeamPermission(context.Background(), team.OrgID, team.ID, resourceID, "Edit")

View File

@@ -237,8 +237,9 @@ func setupTestEnvironment(t *testing.T, ops Options) (*Service, *sqlstore.SQLSto
sql := db.InitTestDB(t) sql := db.InitTestDB(t)
cfg := setting.NewCfg() cfg := setting.NewCfg()
teamSvc := teamimpl.ProvideService(sql, cfg) teamSvc, err := teamimpl.ProvideService(sql, cfg)
userSvc, err := userimpl.ProvideService(sql, nil, cfg, teamimpl.ProvideService(sql, cfg), nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService()) require.NoError(t, err)
userSvc, err := userimpl.ProvideService(sql, nil, cfg, teamSvc, nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService())
require.NoError(t, err) require.NoError(t, err)
license := licensingtest.NewFakeLicensing() license := licensingtest.NewFakeLicensing()
license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe() license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe()

View File

@@ -138,7 +138,8 @@ func GenerateDatasourcePermissions(b *testing.B, db *sqlstore.SQLStore, ac *stor
} }
func generateTeamsAndUsers(b *testing.B, db *sqlstore.SQLStore, users int) ([]int64, []int64) { func generateTeamsAndUsers(b *testing.B, db *sqlstore.SQLStore, users int) ([]int64, []int64) {
teamSvc := teamimpl.ProvideService(db, db.Cfg) teamSvc, err := teamimpl.ProvideService(db, db.Cfg)
require.NoError(b, err)
numberOfTeams := int(math.Ceil(float64(users) / UsersPerTeam)) numberOfTeams := int(math.Ceil(float64(users) / UsersPerTeam))
globalUserId := 0 globalUserId := 0
qs := quotatest.New(false, nil) qs := quotatest.New(false, nil)

View File

@@ -57,7 +57,8 @@ func NewTestMigrationStore(t testing.TB, sqlStore *sqlstore.SQLStore, cfg *setti
license := licensingtest.NewFakeLicensing() license := licensingtest.NewFakeLicensing()
license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe() license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe()
teamSvc := teamimpl.ProvideService(sqlStore, cfg) teamSvc, err := teamimpl.ProvideService(sqlStore, cfg)
require.NoError(t, err)
orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService)
require.NoError(t, err) require.NoError(t, err)
userSvc, err := userimpl.ProvideService(sqlStore, orgService, cfg, teamSvc, cache, quotaService, bundleregistry.ProvideService()) userSvc, err := userimpl.ProvideService(sqlStore, orgService, cfg, teamSvc, cache, quotaService, bundleregistry.ProvideService())

View File

@@ -11,6 +11,7 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol" ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@@ -600,3 +601,25 @@ func (ss *xormStore) getTeamMembers(ctx context.Context, query *team.GetTeamMemb
func (ss *xormStore) RegisterDelete(query string) { func (ss *xormStore) RegisterDelete(query string) {
ss.deletes = append(ss.deletes, query) ss.deletes = append(ss.deletes, query)
} }
// This is just to ensure that all teams have a valid uid.
// To protect against upgrade / downgrade we need to run this for a couple of releases.
// FIXME: Remove this migration and make uid field required https://github.com/grafana/identity-access-team/issues/552
func (ss *xormStore) uidMigration() error {
return ss.db.WithDbSession(context.Background(), func(sess *db.Session) error {
switch ss.db.GetDBType() {
case migrator.SQLite:
_, err := sess.Exec("UPDATE team SET uid=printf('t%09d',id) WHERE uid IS NULL;")
return err
case migrator.Postgres:
_, err := sess.Exec("UPDATE team SET uid='t' || lpad('' || id::text,9,'0') WHERE uid IS NULL;")
return err
case migrator.MySQL:
_, err := sess.Exec("UPDATE team SET uid=concat('t',lpad(id,9,'0')) WHERE uid IS NULL;")
return err
default:
// this branch should be unreachable
return nil
}
})
}

View File

@@ -35,7 +35,8 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) {
} }
t.Run("Testing Team commands and queries", func(t *testing.T) { t.Run("Testing Team commands and queries", func(t *testing.T) {
sqlStore := db.InitTestDB(t) sqlStore := db.InitTestDB(t)
teamSvc := ProvideService(sqlStore, sqlStore.Cfg) teamSvc, err := ProvideService(sqlStore, sqlStore.Cfg)
require.NoError(t, err)
testUser := &user.SignedInUser{ testUser := &user.SignedInUser{
OrgID: 1, OrgID: 1,
Permissions: map[int64]map[string][]string{ Permissions: map[int64]map[string][]string{
@@ -489,7 +490,8 @@ func TestIntegrationSQLStore_SearchTeams(t *testing.T) {
} }
store := db.InitTestDB(t, db.InitTestDBOpt{}) store := db.InitTestDB(t, db.InitTestDBOpt{})
teamSvc := ProvideService(store, store.Cfg) teamSvc, err := ProvideService(store, store.Cfg)
require.NoError(t, err)
// Seed 10 teams // Seed 10 teams
for i := 1; i <= 10; i++ { for i := 1; i <= 10; i++ {
@@ -525,7 +527,8 @@ func TestIntegrationSQLStore_GetTeamMembers_ACFilter(t *testing.T) {
// Seed 2 teams with 2 members // Seed 2 teams with 2 members
setup := func(store *sqlstore.SQLStore) { setup := func(store *sqlstore.SQLStore) {
teamSvc := ProvideService(store, store.Cfg) teamSvc, err := ProvideService(store, store.Cfg)
require.NoError(t, err)
team1, errCreateTeam := teamSvc.CreateTeam("group1 name", "test1@example.org", testOrgID) team1, errCreateTeam := teamSvc.CreateTeam("group1 name", "test1@example.org", testOrgID)
require.NoError(t, errCreateTeam) require.NoError(t, errCreateTeam)
team2, errCreateTeam := teamSvc.CreateTeam("group2 name", "test2@example.org", testOrgID) team2, errCreateTeam := teamSvc.CreateTeam("group2 name", "test2@example.org", testOrgID)
@@ -559,7 +562,8 @@ func TestIntegrationSQLStore_GetTeamMembers_ACFilter(t *testing.T) {
store := db.InitTestDB(t, db.InitTestDBOpt{}) store := db.InitTestDB(t, db.InitTestDBOpt{})
setup(store) setup(store)
teamSvc := ProvideService(store, store.Cfg) teamSvc, err := ProvideService(store, store.Cfg)
require.NoError(t, err)
type getTeamMembersTestCase struct { type getTeamMembersTestCase struct {
desc string desc string

View File

@@ -13,8 +13,13 @@ type Service struct {
store store store store
} }
func ProvideService(db db.DB, cfg *setting.Cfg) team.Service { func ProvideService(db db.DB, cfg *setting.Cfg) (team.Service, error) {
return &Service{store: &xormStore{db: db, cfg: cfg, deletes: []string{}}} store := &xormStore{db: db, cfg: cfg, deletes: []string{}}
if err := store.uidMigration(); err != nil {
return nil, err
}
return &Service{store: &xormStore{db: db, cfg: cfg, deletes: []string{}}}, nil
} }
func (s *Service) CreateTeam(name, email string, orgID int64) (team.Team, error) { func (s *Service) CreateTeam(name, email string, orgID int64) (team.Team, error) {

View File

@@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/supportbundles" "github.com/grafana/grafana/pkg/services/supportbundles"
"github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
@@ -60,6 +61,10 @@ func ProvideService(
return s, err return s, err
} }
if err := s.uidMigration(db); err != nil {
return nil, err
}
bundleRegistry.RegisterSupportItemCollector(s.supportBundleCollector()) bundleRegistry.RegisterSupportItemCollector(s.supportBundleCollector())
return s, nil return s, nil
} }
@@ -485,3 +490,25 @@ func (s *Service) supportBundleCollector() supportbundles.Collector {
Fn: collectorFn, Fn: collectorFn,
} }
} }
// This is just to ensure that all users have a valid uid.
// To protect against upgrade / downgrade we need to run this for a couple of releases.
// FIXME: Remove this migration and make uid field required https://github.com/grafana/identity-access-team/issues/552
func (s *Service) uidMigration(store db.DB) error {
return store.WithDbSession(context.Background(), func(sess *db.Session) error {
switch store.GetDBType() {
case migrator.SQLite:
_, err := sess.Exec("UPDATE user SET uid=printf('u%09d',id) WHERE uid IS NULL;")
return err
case migrator.Postgres:
_, err := sess.Exec("UPDATE `user` SET uid='u' || lpad('' || id::text,9,'0') WHERE uid IS NULL;")
return err
case migrator.MySQL:
_, err := sess.Exec("UPDATE user SET uid=concat('u',lpad(id,9,'0')) WHERE uid IS NULL;")
return err
default:
// this branch should be unreachable
return nil
}
})
}

View File

@@ -377,7 +377,9 @@ func (c K8sTestHelper) createTestUsers(orgName string) OrgUsers {
store.Cfg.AutoAssignOrg = true store.Cfg.AutoAssignOrg = true
store.Cfg.AutoAssignOrgId = int(orgId) store.Cfg.AutoAssignOrgId = int(orgId)
teamSvc := teamimpl.ProvideService(store, store.Cfg) teamSvc, err := teamimpl.ProvideService(store, store.Cfg)
require.NoError(c.t, err)
cache := localcache.ProvideService() cache := localcache.ProvideService()
userSvc, err := userimpl.ProvideService(store, userSvc, err := userimpl.ProvideService(store,
orgService, store.Cfg, teamSvc, cache, quotaService, orgService, store.Cfg, teamSvc, cache, quotaService,