diff --git a/pkg/apis/identity/project.go b/pkg/apis/identity/project.go index 35eaf2b374..9b716a4774 100644 --- a/pkg/apis/identity/project.go +++ b/pkg/apis/identity/project.go @@ -32,6 +32,13 @@ type ProjectDetails struct { SProject + // 项目管理员名称 + Admin string `json:"admin"` + // 项目管理员域ID + AdminDomainId string `json:"admin_domain_id"` + // 项目管理员域名称 + AdminDomain string `json:"admin_domain"` + // 加入项目的用户组数量 GroupCount int `json:"group_count"` // 加入项目的用户数量 diff --git a/pkg/keystone/models/assignments.go b/pkg/keystone/models/assignments.go index c532326916..85fd362183 100644 --- a/pkg/keystone/models/assignments.go +++ b/pkg/keystone/models/assignments.go @@ -22,6 +22,7 @@ import ( "time" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/rbacscope" @@ -32,6 +33,7 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/keystone/options" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/util/stringutils2" @@ -238,10 +240,17 @@ func (manager *SAssignmentManager) fetchUserProjectIdsQuery(userId string) *sqlc } func (manager *SAssignmentManager) fetchProjectUserIdsQuery(projId string) *sqlchemy.SQuery { + return manager.fetchProjectRoleUserIdsQuery(projId, "") +} + +func (manager *SAssignmentManager) fetchProjectRoleUserIdsQuery(projId, roleId string) *sqlchemy.SQuery { q1 := manager.Query("actor_id") q1 = q1.Equals("type", api.AssignmentUserProject) q1 = q1.Equals("target_id", projId) q1 = q1.IsFalse("inherited") + if len(roleId) > 0 { + q1 = q1.Equals("role_id", roleId) + } assigns := AssignmentManager.Query().SubQuery() usergroups := UsergroupManager.Query().SubQuery() @@ -253,6 +262,9 @@ func (manager *SAssignmentManager) fetchProjectUserIdsQuery(projId string) *sqlc q2 = q2.Filter(sqlchemy.Equals(assigns.Field("type"), api.AssignmentGroupProject)) q2 = q2.Filter(sqlchemy.Equals(assigns.Field("target_id"), projId)) q2 = q2.Filter(sqlchemy.IsFalse(assigns.Field("inherited"))) + if len(roleId) > 0 { + q2 = q2.Equals("role_id", roleId) + } union := sqlchemy.Union(q1, q2) return union.Query().Distinct() @@ -344,9 +356,33 @@ func (manager *SAssignmentManager) ProjectAddUser(ctx context.Context, userCred } db.OpsLog.LogEvent(user, db.ACT_ATTACH, project.GetShortDesc(ctx), userCred) db.OpsLog.LogEvent(project, db.ACT_ATTACH, user.GetShortDesc(ctx), userCred) + if len(project.AdminId) == 0 && role.Name == options.Options.ProjectAdminRole { + err := project.resetAdminUser() + if err != nil { + log.Errorf("rsetAdminUser fail: %s", err) + } + } return nil } +func (assign *SAssignment) getRole() (*SRole, error) { + return RoleManager.FetchRoleById(assign.RoleId) +} + +func (assign *SAssignment) getProject() (*SProject, error) { + if assign.Type == api.AssignmentUserProject || assign.Type == api.AssignmentGroupProject { + return ProjectManager.FetchProjectById(assign.TargetId) + } + return nil, nil +} + +func (assign *SAssignment) getDomain() (*SDomain, error) { + if assign.Type == api.AssignmentUserDomain || assign.Type == api.AssignmentGroupDomain { + return DomainManager.FetchDomainById(assign.TargetId) + } + return nil, nil +} + func (manager *SAssignmentManager) batchRemove(actorId string, typeStrs []string) error { q := manager.Query() q = q.In("type", typeStrs) @@ -365,6 +401,17 @@ func (manager *SAssignmentManager) batchRemove(actorId string, typeStrs []string if err != nil { return errors.Wrap(err, "db.Update") } + // clear project admin Id + role, _ := assigns[i].getRole() + if role.Name == options.Options.ProjectAdminRole { + project, _ := assigns[i].getProject() + if project != nil && project.AdminId == actorId { + err := project.resetAdminUser() + if err != nil { + log.Errorf("batchRemove project resetAdminUser fail %s", err) + } + } + } } return nil } @@ -419,6 +466,12 @@ func (manager *SAssignmentManager) projectRemoveUser(ctx context.Context, userCr } db.OpsLog.LogEvent(user, db.ACT_DETACH, project.GetShortDesc(ctx), userCred) db.OpsLog.LogEvent(project, db.ACT_DETACH, user.GetShortDesc(ctx), userCred) + if project.AdminId == user.Id && role.Name == options.Options.ProjectAdminRole { + err := project.resetAdminUser() + if err != nil { + log.Errorf("resetAdminUser fail %s", err) + } + } return nil } @@ -452,6 +505,12 @@ func (manager *SAssignmentManager) projectAddGroup(ctx context.Context, userCred } db.OpsLog.LogEvent(group, db.ACT_ATTACH, project.GetShortDesc(ctx), userCred) db.OpsLog.LogEvent(project, db.ACT_ATTACH, group.GetShortDesc(ctx), userCred) + if len(project.AdminId) == 0 && role.Name == options.Options.ProjectAdminRole { + err := project.resetAdminUser() + if err != nil { + log.Errorf("rsetAdminUser fail: %s", err) + } + } return nil } @@ -474,6 +533,12 @@ func (manager *SAssignmentManager) projectRemoveGroup(ctx context.Context, userC } db.OpsLog.LogEvent(group, db.ACT_DETACH, project.GetShortDesc(ctx), userCred) db.OpsLog.LogEvent(project, db.ACT_DETACH, group.GetShortDesc(ctx), userCred) + if len(project.AdminId) > 0 && role.Name == options.Options.ProjectAdminRole { + err := project.resetAdminUser() + if err != nil { + log.Errorf("rsetAdminUser fail: %s", err) + } + } return nil } diff --git a/pkg/keystone/models/domains.go b/pkg/keystone/models/domains.go index 9f1f0a65b6..c9b45de9bb 100644 --- a/pkg/keystone/models/domains.go +++ b/pkg/keystone/models/domains.go @@ -71,6 +71,8 @@ type SDomain struct { DomainId string `width:"64" charset:"ascii" default:"default" nullable:"false" index:"true"` ParentId string `width:"64" charset:"ascii"` + + AdminId string `width:"64" charset:"ascii" nullable:"true"` } func (manager *SDomainManager) InitializeData() error { @@ -110,9 +112,29 @@ func (manager *SDomainManager) InitializeData() error { } else if err != nil { return err } + /*err = manager.initAdminUsers(context.TODO()) + if err != nil { + return errors.Wrap(err, "initAdminUsers") + }*/ return nil } +/*func (manager *SDomainManager) initAdminUsers(ctx context.Context) error { + q := manager.Query().IsNullOrEmpty("admin_id") + domains := make([]SDomain, 0) + err := db.FetchModelObjects(manager, q, &domains) + if err != nil { + return errors.Wrap(err, "FetchModelObjects") + } + for i := range domains { + err := domains[i].initAdminUser(ctx) + if err != nil { + return errors.Wrap(err, "domains initAdmin") + } + } + return nil +}*/ + func (manager *SDomainManager) Query(fields ...string) *sqlchemy.SQuery { return manager.SStandaloneResourceBaseManager.Query(fields...).IsTrue("is_domain") } diff --git a/pkg/keystone/models/projects.go b/pkg/keystone/models/projects.go index 88ad591f17..3a295ee9b3 100644 --- a/pkg/keystone/models/projects.go +++ b/pkg/keystone/models/projects.go @@ -82,6 +82,8 @@ type SProject struct { // 该项目是否为域(domain) IsDomain tristate.TriState `default:"false"` + + AdminId string `width:"64" charset:"ascii" nullable:"true" list:"domain"` } func (manager *SProjectManager) GetContextManagers() [][]db.IModelManager { @@ -92,7 +94,16 @@ func (manager *SProjectManager) GetContextManagers() [][]db.IModelManager { } func (manager *SProjectManager) InitializeData() error { - return manager.initSysProject(context.TODO()) + ctx := context.TODO() + err := manager.initSysProject(ctx) + if err != nil { + return errors.Wrap(err, "initSysProject") + } + err = manager.initAdminUsers(ctx) + if err != nil { + return errors.Wrap(err, "initAdminUsers") + } + return nil } func (manager *SProjectManager) initSysProject(ctx context.Context) error { @@ -126,6 +137,45 @@ func (manager *SProjectManager) initSysProject(ctx context.Context) error { return nil } +func (manager *SProjectManager) initAdminUsers(ctx context.Context) error { + q := manager.Query().IsNullOrEmpty("admin_id") + projects := make([]SProject, 0) + err := db.FetchModelObjects(manager, q, &projects) + if err != nil { + return errors.Wrap(err, "FetchModelObjects") + } + for i := range projects { + err := projects[i].resetAdminUser() + if err != nil { + return errors.Wrap(err, "projects initAdmin") + } + } + return nil +} + +func (project *SProject) resetAdminUser() error { + role, err := RoleManager.FetchRoleByName(options.Options.ProjectAdminRole, "", "") + if err != nil { + return errors.Wrapf(err, "FetchRoleByName %s", options.Options.ProjectAdminRole) + } + q := AssignmentManager.fetchProjectRoleUserIdsQuery(project.Id, role.Id) + userId := struct { + ActorId string + }{} + err = q.First(&userId) + if err != nil && errors.Cause(err) != sql.ErrNoRows { + return errors.Wrap(err, "query") + } + _, err = db.Update(project, func() error { + project.AdminId = userId.ActorId + return nil + }) + if err != nil { + return errors.Wrap(err, "update") + } + return nil +} + func (manager *SProjectManager) Query(fields ...string) *sqlchemy.SQuery { return manager.SIdentityBaseResourceManager.Query(fields...).IsFalse("is_domain") } @@ -384,12 +434,16 @@ func (manager *SProjectManager) FetchCustomizeColumns( identRows := manager.SIdentityBaseResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) projIds := make([]string, len(objs)) + adminUserIds := make([]string, 0) for i := range rows { rows[i] = api.ProjectDetails{ IdentityBaseResourceDetails: identRows[i], } proj := objs[i].(*SProject) projIds[i] = proj.Id + if len(proj.AdminId) > 0 { + adminUserIds = append(adminUserIds, proj.AdminId) + } } extResource, extLastUpdate, err := ScopeResourceManager.FetchProjectsScopeResources(projIds) @@ -401,6 +455,13 @@ func (manager *SProjectManager) FetchCustomizeColumns( if err != nil { return rows } + + userMaps := make(map[string]SUser) + err = db.FetchModelObjectsByIds(UserManager, "id", adminUserIds, &userMaps) + if err != nil { + log.Errorf("FetchModelObjectsByIds fail %s", err) + } + for i := range rows { groups, _ := groupCnt[projIds[i]] users, _ := userCnt[projIds[i]] @@ -416,7 +477,14 @@ func (manager *SProjectManager) FetchCustomizeColumns( nextUpdate := rows[i].ExtResourcesLastUpdate.Add(time.Duration(options.Options.FetchScopeResourceCountIntervalSeconds) * time.Second) rows[i].ExtResourcesNextUpdate = nextUpdate } - + proj := objs[i].(*SProject) + if len(proj.AdminId) > 0 { + if user, ok := userMaps[proj.AdminId]; ok { + rows[i].Admin = user.Name + rows[i].AdminDomain = user.GetDomain().Name + rows[i].AdminDomainId = user.DomainId + } + } } return rows diff --git a/pkg/keystone/options/options.go b/pkg/keystone/options/options.go index f3b66cbe11..ee89039450 100644 --- a/pkg/keystone/options/options.go +++ b/pkg/keystone/options/options.go @@ -67,6 +67,8 @@ type SKeystoneOptions struct { NoPolicyViolationCheck bool `help:"do not check policy violation when modify or assign policy" default:"false"` LdapSearchPageSize uint32 `help:"pagination size for LDAP search" default:"100"` + + ProjectAdminRole string `help:"name of role to be saved as admin user of project" default:"project_owner"` } var (