diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go index dd2cbc7a23..dfb47d1c88 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go @@ -99,6 +99,7 @@ func (c *CollaborationModeColl) Update(username string, args *models.Collaborati "update_time": time.Now().Unix(), "update_by": username, "members": args.Members, + "member_info": args.MemberInfo, "workflows": args.Workflows, "recycle_day": args.RecycleDay, "revision": res.Revision + 1, diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 4bd495d413..87ee4d8dd9 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -36,6 +36,7 @@ import ( config2 "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/label/config" workflowservice "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/v2/pkg/setting" + "github.com/koderover/zadig/v2/pkg/shared/client/user" "github.com/koderover/zadig/v2/pkg/tool/log" ) @@ -296,9 +297,20 @@ func updateVisitTime(uid string, cis []*models.CollaborationInstance, logger *za } func GetCollaborationUpdate(projectName, uid, identityType, userName string, logger *zap.SugaredLogger) (*GetCollaborationUpdateResp, error) { + relatedGroups, err := user.New().GetUserGroupsByUid(uid) + if err != nil { + logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) + return nil, err + } + members := []string{uid} + for _, group := range relatedGroups.GroupList { + members = append(members, group.ID) + } + + // user uid and related gids to get collaboration mode collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeListOptions{ Projects: []string{projectName}, - Members: []string{uid}, + Members: members, }) if err != nil { logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go index 2638f42c12..740cfdc60e 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go @@ -17,14 +17,32 @@ limitations under the License. package service import ( + "fmt" + "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/collaboration/repository/mongodb" ) +func validateMemberInfo(collaborationMode *models.CollaborationMode) bool { + if len(collaborationMode.Members) != len(collaborationMode.MemberInfo) { + return false + } + memberSet := sets.NewString(collaborationMode.Members...) + memberInfoSet := sets.NewString() + for _, memberInfo := range collaborationMode.MemberInfo { + memberInfoSet.Insert(memberInfo.GetID()) + } + return memberSet.Equal(memberInfoSet) +} + func CreateCollaborationMode(userName string, collaborationMode *models.CollaborationMode, logger *zap.SugaredLogger) error { + if !validateMemberInfo(collaborationMode) { + return fmt.Errorf("members and member_info not match") + } err := mongodb.NewCollaborationModeColl().Create(userName, collaborationMode) if err != nil { logger.Errorf("CreateCollaborationMode error, err msg:%s", err) @@ -34,6 +52,9 @@ func CreateCollaborationMode(userName string, collaborationMode *models.Collabor } func UpdateCollaborationMode(userName string, collaborationMode *models.CollaborationMode, logger *zap.SugaredLogger) error { + if !validateMemberInfo(collaborationMode) { + return fmt.Errorf("members and member_info not match") + } err := mongodb.NewCollaborationModeColl().Update(userName, collaborationMode) if err != nil { logger.Errorf("UpdateCollaborationMode error, err msg:%s", err) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index fdf554d2f3..5d4c46e2dd 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -110,10 +110,11 @@ type Approval struct { } type NativeApproval struct { - Timeout int `bson:"timeout" yaml:"timeout" json:"timeout"` - ApproveUsers []*User `bson:"approve_users" yaml:"approve_users" json:"approve_users"` - NeededApprovers int `bson:"needed_approvers" yaml:"needed_approvers" json:"needed_approvers"` - RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"` + Timeout int `bson:"timeout" yaml:"timeout" json:"timeout"` + ApproveUsers []*User `bson:"approve_users" yaml:"approve_users" json:"approve_users"` + FloatApproveUsers []*User `bson:"-" yaml:"flat_approve_users" json:"flat_approve_users"` + NeededApprovers int `bson:"needed_approvers" yaml:"needed_approvers" json:"needed_approvers"` + RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"` // InstanceCode: native approval instance code, save for working after restart aslan InstanceCode string `bson:"instance_code" yaml:"instance_code" json:"instance_code"` } diff --git a/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go b/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go index 6aa9c17ceb..dccdfe4b42 100644 --- a/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go +++ b/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go @@ -154,16 +154,23 @@ func setCollaborationModesWorkflowDisplayName(mode *models.CollaborationMode) { } func setMemberInfo(mode *models.CollaborationMode) { - if mode.MemberInfo != nil && len(mode.MemberInfo) > 0 { + if mode.MemberInfo != nil && len(mode.MemberInfo) == len(mode.Members) { return } - memberList := make([]*types.Identity, 0) + memberInfoMap := make(map[string]*types.Identity) + for _, member := range mode.MemberInfo { + memberInfoMap[member.UID] = member + } + for _, uid := range mode.Members { - memberList = append(memberList, &types.Identity{ + if _, ok := memberInfoMap[uid]; ok { + continue + } + mode.MemberInfo = append(mode.MemberInfo, &types.Identity{ IdentityType: "user", UID: uid, }) } - mode.MemberInfo = memberList + } diff --git a/pkg/microservice/aslan/core/release_plan/service/approval.go b/pkg/microservice/aslan/core/release_plan/service/approval.go index 5dbefbf127..981c1b5949 100644 --- a/pkg/microservice/aslan/core/release_plan/service/approval.go +++ b/pkg/microservice/aslan/core/release_plan/service/approval.go @@ -26,7 +26,12 @@ import ( "time" "github.com/google/uuid" + "github.com/koderover/zadig/v2/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/v2/pkg/shared/client/user" + "github.com/koderover/zadig/v2/pkg/tool/mail" + "github.com/koderover/zadig/v2/pkg/types" "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/sets" configbase "github.com/koderover/zadig/v2/pkg/config" "github.com/koderover/zadig/v2/pkg/microservice/aslan/config" @@ -35,12 +40,9 @@ import ( approvalservice "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/approval" dingservice "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/dingtalk" larkservice "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/lark" - "github.com/koderover/zadig/v2/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/v2/pkg/shared/client/user" "github.com/koderover/zadig/v2/pkg/tool/dingtalk" "github.com/koderover/zadig/v2/pkg/tool/lark" "github.com/koderover/zadig/v2/pkg/tool/log" - "github.com/koderover/zadig/v2/pkg/tool/mail" ) //go:embed approval.html @@ -210,48 +212,114 @@ func updateDingTalkApproval(ctx context.Context, approvalInfo *models.Approval) return nil } +func geneFlatNativeApprovalUsers(approval *models.NativeApproval) ([]*models.User, map[string]*types.UserInfo) { + // change [group + user] approvals to user approvals + approvalUsers := make([]*models.User, 0) + userSet := sets.NewString() + userMap := make(map[string]*types.UserInfo) + + if approval == nil { + return approvalUsers, userMap + } + + for _, u := range approval.ApproveUsers { + if u.Type == "user" || u.Type == "" { + userSet.Insert(u.UserID) + approvalUsers = append(approvalUsers, u) + } + } + for _, u := range approval.ApproveUsers { + if u.Type == "group" { + groupInfo, err := user.New().GetGroupDetailedInfo(u.GroupID) + if err != nil { + log.Warnf("CreateNativeApproval GetGroupDetailedInfo error, error msg:%s", err) + continue + } + for _, uid := range groupInfo.UIDs { + if userSet.Has(uid) { + continue + } + userSet.Insert(uid) + userDetailedInfo, err := user.New().GetUserByID(uid) + if err != nil { + log.Errorf("failed to find user %s, error: %s", uid, err) + continue + } + userMap[uid] = userDetailedInfo + approvalUsers = append(approvalUsers, &models.User{ + Type: "user", + UserID: uid, + UserName: userDetailedInfo.Name, + }) + } + } + } + + return approvalUsers, userMap +} + func createNativeApproval(plan *models.ReleasePlan, url string) error { if plan == nil || plan.Approval == nil || plan.Approval.NativeApproval == nil { return errors.New("createNativeApproval: native approval data not found") } approval := plan.Approval.NativeApproval + approvalUsers, userMap := geneFlatNativeApprovalUsers(approval) + + // send email to all approval users if necessary go func() { - email, err := systemconfig.New().GetEmailHost() - if err != nil { - log.Errorf("CreateNativeApproval GetEmailHost error, error msg:%s", err) - return - } + var err error + mailNotifyInfo := "" + var email *systemconfig.Email - t, err := template.New("approval").Parse(string(approvalHTML)) - if err != nil { - log.Errorf("CreateNativeApproval template parse error, error msg:%s", err) - return + for { + email, err = systemconfig.New().GetEmailHost() + if err != nil { + log.Errorf("CreateNativeApproval GetEmailHost error, error msg:%s", err) + break + } + + t, err := template.New("approval").Parse(string(approvalHTML)) + if err != nil { + log.Errorf("CreateNativeApproval template parse error, error msg:%s", err) + break + } + var buf bytes.Buffer + err = t.Execute(&buf, struct { + PlanName string + Manager string + Description string + TimeRange string + Url string + }{ + PlanName: plan.Name, + Manager: plan.Manager, + Description: plan.Description, + TimeRange: time.Unix(plan.StartTime, 0).Format("2006-01-02 15:04:05") + "-" + time.Unix(plan.EndTime, 0).Format("2006-01-02 15:04:05"), + Url: url, + }) + if err != nil { + log.Errorf("CreateNativeApproval template execute error, error msg:%s", err) + break + } + mailNotifyInfo = buf.String() + break } - var buf bytes.Buffer - err = t.Execute(&buf, struct { - PlanName string - Manager string - Description string - TimeRange string - Url string - }{ - PlanName: plan.Name, - Manager: plan.Manager, - Description: plan.Description, - TimeRange: time.Unix(plan.StartTime, 0).Format("2006-01-02 15:04:05") + "-" + time.Unix(plan.EndTime, 0).Format("2006-01-02 15:04:05"), - Url: url, - }) - if err != nil { - log.Errorf("CreateNativeApproval template execute error, error msg:%s", err) + + if email == nil { return } - for _, u := range approval.ApproveUsers { - info, err := user.New().GetUserByID(u.UserID) - if err != nil { - log.Warnf("CreateNativeApproval GetUserByUid error, error msg:%s", err) - continue + + for _, u := range approvalUsers { + info, ok := userMap[u.UserID] + if !ok { + info, err = user.New().GetUserByID(u.UserID) + if err != nil { + log.Warnf("CreateNativeApproval GetUserByUid error, error msg:%s", err) + continue + } } + if info.Email == "" { log.Warnf("CreateNativeApproval user %s email is empty", info.Name) continue @@ -264,7 +332,7 @@ func createNativeApproval(plan *models.ReleasePlan, url string) error { UserName: email.UserName, Password: email.Password, Port: email.Port, - Body: buf.String(), + Body: mailNotifyInfo, }) if err != nil { log.Errorf("CreateNativeApproval SendEmail error, error msg:%s", err) @@ -273,10 +341,14 @@ func createNativeApproval(plan *models.ReleasePlan, url string) error { } }() + originApprovalUser := approval.ApproveUsers + approval.ApproveUsers = approvalUsers + approveKey := uuid.New().String() approval.InstanceCode = approveKey - approveWithL := approval - approvalservice.GlobalApproveMap.SetApproval(approveKey, approveWithL) + + approvalservice.GlobalApproveMap.SetApproval(approveKey, approval) + approval.ApproveUsers = originApprovalUser return nil } diff --git a/pkg/microservice/aslan/core/release_plan/service/release_plan.go b/pkg/microservice/aslan/core/release_plan/service/release_plan.go index 015413311c..96dde5b1eb 100644 --- a/pkg/microservice/aslan/core/release_plan/service/release_plan.go +++ b/pkg/microservice/aslan/core/release_plan/service/release_plan.go @@ -135,7 +135,14 @@ func ListReleasePlans(pageNum, pageSize int64) (*ListReleasePlanResp, error) { func GetReleasePlan(id string) (*models.ReleasePlan, error) { releasePlan, err := mongodb.NewReleasePlanColl().GetByID(context.Background(), id) if err != nil { - return nil, err + return nil, errors.Wrap(err, "GetReleasePlan") + } + + // native approval users may be user or user groups + // convert to flat user when needed, this data is generated dynamically because group binding may be changed + if releasePlan.Approval != nil && releasePlan.Approval.NativeApproval != nil { + flatNativeApprovalUsers, _ := geneFlatNativeApprovalUsers(releasePlan.Approval.NativeApproval) + releasePlan.Approval.NativeApproval.FloatApproveUsers = flatNativeApprovalUsers } for _, releasePlanJob := range releasePlan.Jobs { @@ -533,7 +540,11 @@ func ApproveReleasePlan(c *handler.Context, planID string, req *ApproveRequest) if !ok { // restore data after restart aslan log.Infof("updateNativeApproval: approval instance code %s not found, set it", plan.Approval.NativeApproval.InstanceCode) + approvalUsers, _ := geneFlatNativeApprovalUsers(plan.Approval.NativeApproval) + originApprovalUsers := plan.Approval.NativeApproval.ApproveUsers + plan.Approval.NativeApproval.ApproveUsers = approvalUsers approvalservice.GlobalApproveMap.SetApproval(plan.Approval.NativeApproval.InstanceCode, plan.Approval.NativeApproval) + plan.Approval.NativeApproval.ApproveUsers = originApprovalUsers } approval, err = approvalservice.GlobalApproveMap.DoApproval(approvalKey, c.UserName, c.UserID, req.Comment, req.Approve) diff --git a/pkg/microservice/user/core/handler/user/user_group.go b/pkg/microservice/user/core/handler/user/user_group.go index 0f7f69ca1e..e717c0a64c 100644 --- a/pkg/microservice/user/core/handler/user/user_group.go +++ b/pkg/microservice/user/core/handler/user/user_group.go @@ -70,6 +70,7 @@ type listUserGroupsReq struct { PageNum int `json:"page_num" form:"page_num"` PageSize int `json:"page_size" form:"page_size"` Name string `json:"name" form:"name"` + Uid string `json:"uid" form:"uid"` } type openAPIListUserGroupReq struct { @@ -136,8 +137,8 @@ func ListUserGroups(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - - if query.PageNum == 0 { + + if query.PageNum == 0 { query.PageNum = 1 } @@ -145,17 +146,31 @@ func ListUserGroups(c *gin.Context) { query.PageSize = 200 } - groupList, count, err := permission.ListUserGroups(query.Name, query.PageNum, query.PageSize, ctx.Logger) - - if err != nil { - ctx.Err = err - return - } - - ctx.Resp = &listUserGroupResp{ - GroupList: groupList, - Count: count, - } + if len(query.Uid) > 0 { + groupList, count, err := permission.ListUserGroupsByUid(query.Uid, ctx.Logger) + + if err != nil { + ctx.Err = err + return + } + + ctx.Resp = &listUserGroupResp{ + GroupList: groupList, + Count: count, + } + } else { + groupList, count, err := permission.ListUserGroups(query.Name, query.PageNum, query.PageSize, ctx.Logger) + + if err != nil { + ctx.Err = err + return + } + + ctx.Resp = &listUserGroupResp{ + GroupList: groupList, + Count: count, + } + } } func GetUserGroup(c *gin.Context) { diff --git a/pkg/microservice/user/core/service/permission/user_group.go b/pkg/microservice/user/core/service/permission/user_group.go index 57e9cdfd30..db7dd1f462 100644 --- a/pkg/microservice/user/core/service/permission/user_group.go +++ b/pkg/microservice/user/core/service/permission/user_group.go @@ -132,6 +132,31 @@ type UserGroupResp struct { UserTotal int64 `json:"user_total"` } +func ListUserGroupsByUid(uid string, logger *zap.SugaredLogger) ([]*UserGroupResp, int64, error) { + groups, err := orm.ListUserGroupByUID(uid, repository.DB) + if err != nil { + logger.Errorf("failed to list user groups by uid: %s, error: %s", uid, err) + return nil, 0, err + } + + resp := make([]*UserGroupResp, 0) + for _, group := range groups { + respItem := &UserGroupResp{ + ID: group.GroupID, + Name: group.GroupName, + Description: group.Description, + } + if group.Type == int64(setting.RoleTypeSystem) { + respItem.Type = string(setting.ResourceTypeSystem) + } else { + respItem.Type = string(setting.ResourceTypeCustom) + } + resp = append(resp, respItem) + } + + return resp, int64(len(resp)), nil +} + func ListUserGroups(queryName string, pageNum, pageSize int, logger *zap.SugaredLogger) ([]*UserGroupResp, int64, error) { resp := make([]*UserGroupResp, 0) tx := repository.DB.Begin() diff --git a/pkg/shared/client/user/user_group.go b/pkg/shared/client/user/user_group.go index 0cf709259d..53bed156a3 100644 --- a/pkg/shared/client/user/user_group.go +++ b/pkg/shared/client/user/user_group.go @@ -35,3 +35,13 @@ func (c *Client) GetGroupDetailedInfo(groupID string) (*types.DetailedUserGroupR return resp, nil } + +func (c *Client) GetUserGroupsByUid(uid string) (*types.ListUserGroupResp, error) { + url := "/user-group" + resp := &types.ListUserGroupResp{} + queries := make(map[string]string) + queries["uid"] = uid + + _, err := c.Get(url, httpclient.SetQueryParams(queries), httpclient.SetResult(resp)) + return resp, err +} diff --git a/pkg/types/user.go b/pkg/types/user.go index 670d553faa..2a004b6bb1 100644 --- a/pkg/types/user.go +++ b/pkg/types/user.go @@ -79,3 +79,11 @@ type Identity struct { UID string `json:"uid,omitempty"` GID string `json:"gid,omitempty"` } + +func (id *Identity) GetID() string { + if id.IdentityType == "user" { + return id.UID + } else { + return id.GID + } +} diff --git a/pkg/types/user_group.go b/pkg/types/user_group.go index bcf4b15ac5..d214fc7832 100644 --- a/pkg/types/user_group.go +++ b/pkg/types/user_group.go @@ -27,3 +27,16 @@ type DetailedUserGroupResp struct { Type string `json:"type"` UIDs []string `json:"uids"` } + +type UserGroupResp struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + UserTotal int64 `json:"user_total"` +} + +type ListUserGroupResp struct { + GroupList []*UserGroupResp `json:"group_list"` + Count int64 `json:"total"` +}