diff --git a/pkg/cloudcommon/notifyclient/notify.go b/pkg/cloudcommon/notifyclient/notify.go index 1c23a1fec2..d2cefde2ed 100644 --- a/pkg/cloudcommon/notifyclient/notify.go +++ b/pkg/cloudcommon/notifyclient/notify.go @@ -149,6 +149,16 @@ func Notify(recipientId []string, isGroup bool, priority npk.TNotifyPriority, ev notify(context.Background(), recipientId, isGroup, priority, event, data) } +func NotifyWithContact(ctx context.Context, contacts []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { + p := sNotifyParams{ + contacts: contacts, + priority: priority, + event: event, + data: data, + } + rawNotify(ctx, p) +} + func notify(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { switch priority { case npk.NotifyPriorityCritical: @@ -161,40 +171,63 @@ func notify(ctx context.Context, recipientId []string, isGroup bool, priority np } func RawNotifyWithCtx(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { - rawNotify(ctx, recipientId, isGroup, channel, priority, event, data) + rawNotify(ctx, sNotifyParams{ + recipientId: recipientId, + isGroup: isGroup, + channel: channel, + priority: priority, + event: event, + data: data, + }) } func RawNotify(recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { - rawNotify(context.Background(), recipientId, isGroup, channel, priority, event, data) + rawNotify(context.Background(), sNotifyParams{ + recipientId: recipientId, + isGroup: isGroup, + channel: channel, + priority: priority, + event: event, + data: data, + }) } // IntelliNotify try to create receiver nonexistent if createReceiver is set to true func IntelliNotify(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, createReceiver bool) { - intelliNotify(ctx, recipientId, isGroup, channel, priority, event, data, createReceiver) + intelliNotify(ctx, sNotifyParams{ + recipientId: recipientId, + isGroup: isGroup, + channel: channel, + priority: priority, + event: event, + data: data, + createReceiver: createReceiver, + }) } const noSuchReceiver = `no such receiver whose uid is '(.*)'` var noSuchReceiverRegexp = regexp.MustCompile(noSuchReceiver) -func intelliNotify(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, createReceiver bool) { - log.Infof("notify %s event %s priority %s", recipientId, event, priority) +func intelliNotify(ctx context.Context, p sNotifyParams) { + log.Infof("notify %s event %s priority %s", p.recipientId, p.event, p.priority) msg := npk.SNotifyMessage{} - if isGroup { - msg.Gid = recipientId + if p.isGroup { + msg.Gid = p.recipientId } else { - msg.Uid = recipientId + msg.Uid = p.recipientId } - msg.Priority = priority - msg.ContactType = channel - topic, _ := getContent(ctx, event, "title", channel, data) + msg.Priority = p.priority + msg.Contacts = p.contacts + msg.ContactType = p.channel + topic, _ := getContent(ctx, p.event, "title", p.channel, p.data) if len(topic) == 0 { - topic = event + topic = p.event } msg.Topic = topic - body, _ := getContent(ctx, event, "content", channel, data) + body, _ := getContent(ctx, p.event, "content", p.channel, p.data) if len(body) == 0 { - body, _ = data.GetString() + body, _ = p.data.GetString() } msg.Msg = body // log.Debugf("send notification %s %s", topic, body) @@ -205,7 +238,7 @@ func intelliNotify(ctx context.Context, recipientId []string, isGroup bool, chan if err == nil { break } - if !createReceiver { + if !p.createReceiver { log.Errorf("unable to send notification: %v", err) break } @@ -236,8 +269,19 @@ func intelliNotify(ctx context.Context, recipientId []string, isGroup bool, chan }, nil, nil) } -func rawNotify(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { - intelliNotify(ctx, recipientId, isGroup, channel, priority, event, data, false) +type sNotifyParams struct { + recipientId []string + isGroup bool + contacts []string + channel npk.TNotifyChannel + priority npk.TNotifyPriority + event string + data jsonutils.JSONObject + createReceiver bool +} + +func rawNotify(ctx context.Context, p sNotifyParams) { + intelliNotify(ctx, p) } func NotifyNormal(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { @@ -249,18 +293,36 @@ func NotifyNormalWithCtx(ctx context.Context, recipientId []string, isGroup bool } func notifyNormal(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { - for _, c := range []npk.TNotifyChannel{ + p := sNotifyParams{ + recipientId: recipientId, + isGroup: isGroup, + event: event, + data: data, + priority: npk.NotifyPriorityNormal, + } + notifyWithChannel(ctx, p, npk.NotifyByEmail, npk.NotifyByDingTalk, - npk.NotifyByWebConsole, npk.NotifyByFeishu, npk.NotifyByWorkwx, - } { - rawNotify(ctx, recipientId, isGroup, - c, - npk.NotifyPriorityNormal, - event, data) + npk.NotifyByWebConsole, + ) +} + +func notifyWithChannel(ctx context.Context, p sNotifyParams, channels ...npk.TNotifyChannel) { + reps := p.recipientId + for _, c := range channels { + p.recipientId = []string{} + p.contacts = []string{} + p.channel = c + if c == npk.NotifyByWebConsole { + p.contacts = reps + } else { + p.recipientId = reps + } + rawNotify(ctx, p) } + } func NotifyImportant(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { @@ -272,19 +334,21 @@ func NotifyImportantWithCtx(ctx context.Context, recipientId []string, isGroup b } func notifyImportant(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { - for _, c := range []npk.TNotifyChannel{ + p := sNotifyParams{ + recipientId: recipientId, + isGroup: isGroup, + event: event, + data: data, + priority: npk.NotifyPriorityNormal, + } + notifyWithChannel(ctx, p, npk.NotifyByEmail, npk.NotifyByDingTalk, npk.NotifyByMobile, npk.NotifyByWebConsole, npk.NotifyByFeishu, npk.NotifyByWorkwx, - } { - rawNotify(ctx, recipientId, isGroup, - c, - npk.NotifyPriorityImportant, - event, data) - } + ) } func NotifyCritical(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { @@ -296,19 +360,21 @@ func NotifyCriticalWithCtx(ctx context.Context, recipientId []string, isGroup bo } func notifyCritical(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { - for _, c := range []npk.TNotifyChannel{ + p := sNotifyParams{ + recipientId: recipientId, + isGroup: isGroup, + event: event, + data: data, + priority: npk.NotifyPriorityNormal, + } + notifyWithChannel(ctx, p, npk.NotifyByEmail, npk.NotifyByDingTalk, npk.NotifyByMobile, npk.NotifyByWebConsole, npk.NotifyByFeishu, npk.NotifyByWorkwx, - } { - rawNotify(ctx, recipientId, isGroup, - c, - npk.NotifyPriorityCritical, - event, data) - } + ) } // NotifyAllWithoutRobot will send messages via all contacnt type from exclude robot contact type such as dingtalk-robot. diff --git a/pkg/mcclient/modules/notify/mod_notification.go b/pkg/mcclient/modules/notify/mod_notification.go index 077f3f985b..ce873f2bf0 100644 --- a/pkg/mcclient/modules/notify/mod_notification.go +++ b/pkg/mcclient/modules/notify/mod_notification.go @@ -32,6 +32,7 @@ type SNotifyMessage struct { Uid []string `json:"uid,omitempty"` Gid []string `json:"gid,omitempty"` ContactType TNotifyChannel `json:"contact_type,omitempty"` + Contacts []string `json:"contracts"` Topic string `json:"topic,omitempty"` Priority TNotifyPriority `json:"priority,omitempty"` Msg string `json:"msg,omitempty"` @@ -41,6 +42,7 @@ type SNotifyMessage struct { type SNotifyV2Message struct { Receivers []string `json:"receivers"` + Contacts []string `json:"contacts"` ContactType string `json:"contact_type"` Topic string `json:"topic"` Priority string `json:"priority"` @@ -74,6 +76,7 @@ func (manager *NotificationManager) Send(s *mcclient.ClientSession, msg SNotifyM v2msg := SNotifyV2Message{ Receivers: receiverIds, + Contacts: msg.Contacts, ContactType: string(msg.ContactType), Topic: msg.Topic, Priority: string(msg.Priority), diff --git a/pkg/notify/models/notification.go b/pkg/notify/models/notification.go index 5d45f9c24e..f9a73ee243 100644 --- a/pkg/notify/models/notification.go +++ b/pkg/notify/models/notification.go @@ -31,6 +31,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/image/policy" "yunion.io/x/onecloud/pkg/mcclient" notifyv2 "yunion.io/x/onecloud/pkg/notify" "yunion.io/x/onecloud/pkg/notify/oldmodels" @@ -71,24 +72,32 @@ type SNotification struct { SendTimes int } +const ( + SendByContact = "send_by_contact" +) + func (nm *SNotificationManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.NotificationCreateInput) (api.NotificationCreateInput, error) { + // compatible + if len(input.Receivers) == 0 && input.ContactType == api.WEBCONSOLE { + input.Contacts = input.Receivers + input.Receivers = []string{} + } + if len(input.Receivers) == 0 { + if !userCred.IsAllow(rbacutils.ScopeSystem, api.SERVICE_TYPE, nm.KeywordPlural(), policy.PolicyActionPerform, SendByContact) { + return input, httperrors.NewForbiddenError("can't send notification by contact, need receiver") + } + if len(input.Contacts) == 0 { + input.Contacts = []string{""} + } + } + // check contact type enabled allContactType, err := ConfigManager.allContactType() if err != nil { return input, err } - switch { - case input.ContactType == api.WEBHOOK: - case utils.IsInStringArray(input.ContactType, ConfigManager.filterContactType(allContactType, "")): - //check uids, rids and contacts - if len(input.Receivers) == 0 && len(input.Contacts) == 0 { - return input, httperrors.NewMissingParameterError("receivers | contacts") - } - case utils.IsInStringArray(input.ContactType, ConfigManager.filterContactType(allContactType, api.CTYPE_ROBOT_ONLY)): - default: - return input, httperrors.NewInputParameterError("Unconfigured contact type %q", input.ContactType) - } if !utils.IsInStringArray(input.ContactType, allContactType) { + return input, httperrors.NewInputParameterError("Unconfigured contact type %q", input.ContactType) } // check receivers if len(input.Receivers) > 0 { @@ -137,6 +146,12 @@ func (n *SNotification) CustomizeCreate(ctx context.Context, userCred mcclient.T return errors.Wrap(err, "ReceiverNotificationManager.Create") } } + for i := range input.Contacts { + _, err := ReceiverNotificationManager.CreateWithoutReceiver(ctx, userCred, input.Contacts[i], n.Id) + if err != nil { + return errors.Wrap(err, "ReceiverNotificationManager.Create") + } + } return nil } @@ -200,7 +215,7 @@ func (n *SNotification) ReceiveDetails() ([]api.ReceiveDetail, error) { subRQ := ReceiverManager.Query("id", "name").SubQuery() q := ReceiverNotificationManager.Query("receiver_id", "notification_id", "contact", "send_at", "send_by", "status", "failed_reason").Equals("notification_id", n.Id) q.AppendField(subRQ.Field("name", "receiver_name")) - q = q.Join(subRQ, sqlchemy.Equals(q.Field("receiver_id"), subRQ.Field("id"))) + q = q.LeftJoin(subRQ, sqlchemy.OR(sqlchemy.Equals(q.Field("receiver_id"), subRQ.Field("id")), sqlchemy.Equals(q.Field("contact"), subRQ.Field("id")))) ret := make([]api.ReceiveDetail, 0, 2) err := q.All(&ret) if err != nil && errors.Cause(err) != sql.ErrNoRows { diff --git a/pkg/notify/models/receiver_notification.go b/pkg/notify/models/receiver_notification.go index 0a9cc8a1c1..1ed199f187 100644 --- a/pkg/notify/models/receiver_notification.go +++ b/pkg/notify/models/receiver_notification.go @@ -31,13 +31,17 @@ type SReceiverNotificationManager struct { db.SJointResourceBaseManager } +const ( + ReceiverIdDefault = "default" +) + // +onecloud:swagger-gen-ignore type SReceiverNotification struct { db.SJointResourceBase ReceiverID string `width:"128" charset:"ascii" nullable:"false"` NotificationID string `width:"128" charset:"ascii" nullable:"false"` - // ignore if ReceiverID is not empty + // ignore if ReceiverID is not empty or default Contact string `width:"128" nullable:"false"` SendAt time.Time `nullable:"false"` SendBy string `width:"128" nullable:"false"` @@ -66,6 +70,7 @@ func (rnm *SReceiverNotificationManager) GetSlaveFieldName() string { func (rnm *SReceiverNotificationManager) CreateWithoutReceiver(ctx context.Context, userCred mcclient.TokenCredential, contact, notificationID string) (*SReceiverNotification, error) { rn := &SReceiverNotification{ NotificationID: notificationID, + ReceiverID: ReceiverIdDefault, Contact: contact, Status: api.RECEIVER_NOTIFICATION_RECEIVED, SendBy: userCred.GetUserId(), diff --git a/pkg/notify/tasks/notifications_send_task.go b/pkg/notify/tasks/notifications_send_task.go index 91974d0af9..80ee236e14 100644 --- a/pkg/notify/tasks/notifications_send_task.go +++ b/pkg/notify/tasks/notifications_send_task.go @@ -49,7 +49,20 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone } notification.SetStatus(self.UserCred, apis.NOTIFICATION_STATUS_SENDING, "") - // sort out what needs to be sent + // split rns + var ( + rnsWithReceiver []*models.SReceiverNotification + rnsWithoutReceiver []*models.SReceiverNotification + ) + + for i := range rns { + if rns[i].ReceiverID == models.ReceiverIdDefault { + rnsWithoutReceiver = append(rnsWithoutReceiver, &rns[i]) + } else { + rnsWithReceiver = append(rnsWithReceiver, &rns[i]) + } + } + failedRecord := make([]string, 0) sendFail := func(rn *models.SReceiverNotification, reason string) { rn.AfterSend(ctx, false, reason) @@ -58,50 +71,54 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone // build contactMap contactMap := make(map[string]*models.SReceiverNotification) - for i := range rns { - if len(rns[i].ReceiverID) == 0 { - contactMap[rns[i].Contact] = &rns[i] + for i := range rnsWithReceiver { + if len(rnsWithReceiver[i].ReceiverID) == 0 { + contactMap[rnsWithReceiver[i].Contact] = rnsWithReceiver[i] continue } - receiver, err := rns[i].Receiver() + receiver, err := rnsWithReceiver[i].Receiver() if err != nil { - sendFail(&rns[i], fmt.Sprintf("fail to fetch Receiver: %s", err.Error())) + sendFail(rnsWithReceiver[i], fmt.Sprintf("fail to fetch Receiver: %s", err.Error())) continue } // check receiver enabled if receiver.Enabled.IsFalse() { - sendFail(&rns[i], fmt.Sprintf("disabled receiver")) + sendFail(rnsWithReceiver[i], fmt.Sprintf("disabled receiver")) continue } // check contact enabled enabled, err := receiver.IsEnabledContactType(notification.ContactType) if err != nil { - sendFail(&rns[i], fmt.Sprintf("IsEnabledContactType error for receiver: %s", err.Error())) + sendFail(rnsWithReceiver[i], fmt.Sprintf("IsEnabledContactType error for receiver: %s", err.Error())) continue } if !enabled { - sendFail(&rns[i], fmt.Sprintf("disabled contactType %q", notification.ContactType)) + sendFail(rnsWithReceiver[i], fmt.Sprintf("disabled contactType %q", notification.ContactType)) continue } // check contact verified verified, err := receiver.IsVerifiedContactType(notification.ContactType) if err != nil { - sendFail(&rns[i], fmt.Sprintf("IsVerifiedContactType error for receiver: %s", err.Error())) + sendFail(rnsWithReceiver[i], fmt.Sprintf("IsVerifiedContactType error for receiver: %s", err.Error())) continue } if !verified { - sendFail(&rns[i], fmt.Sprintf("unverified contactType %q", notification.ContactType)) + sendFail(rnsWithReceiver[i], fmt.Sprintf("unverified contactType %q", notification.ContactType)) continue } contact, err := receiver.GetContact(notification.ContactType) if err != nil { reason := fmt.Sprintf("fail to fetch contact: %s", err.Error()) - sendFail(&rns[i], reason) + sendFail(rnsWithReceiver[i], reason) continue } - contactMap[contact] = &rns[i] + contactMap[contact] = rnsWithReceiver[i] + } + + for i := range rnsWithoutReceiver { + contactMap[rnsWithoutReceiver[i].Contact] = rnsWithoutReceiver[i] } // set status before send