From 1ed65ddd1aace3376914828fd49c9348bf0fdade Mon Sep 17 00:00:00 2001 From: Rain Zheng Date: Wed, 2 Sep 2020 16:10:06 +0800 Subject: [PATCH] Feature & Fix for Notify (#7714) * feat(notify): Add 'get-types' action for ConfigManager The List interface is only open to administrators, because we don't want anyone other than administrators to see the details of the notify configuration. To let general user to kown available notification channels, add 'get-types' actions of ConfigManager. * feat(notify): Check the availability of contact type before creating notification Creating notification with unconfigured contact type will fail at sending message. Now, prevent creation in advance. * feat(notify): Add workwx for NotifyNormal/Important/Critical * feat(notify): Add Notify about robot in notifyclient * fix(notify): Init ParentContactType for SSubContact 1. ParentContactType of SSubContact with webconsole type is empty. 2. ParentContactType of others SSubContact is mobile. * fix(notify): Unverify SSubContact when ParentContactType match * fix(notify): When no data is initialized, exit directly --- cmd/climc/shell/notifyv2/config.go | 12 +++++++ pkg/apis/notify/config.go | 11 ++++++ pkg/cloudcommon/notifyclient/notify.go | 29 +++++++++++++++ pkg/mcclient/modules/notify/consts.go | 1 + pkg/notify/models/config.go | 50 ++++++++++++++++++++++++++ pkg/notify/models/notification.go | 9 +++++ pkg/notify/models/receiver.go | 30 ++++++++++++---- 7 files changed, 136 insertions(+), 6 deletions(-) diff --git a/cmd/climc/shell/notifyv2/config.go b/cmd/climc/shell/notifyv2/config.go index 9a2e494a62..3db4eb8b9a 100644 --- a/cmd/climc/shell/notifyv2/config.go +++ b/cmd/climc/shell/notifyv2/config.go @@ -106,6 +106,18 @@ func init() { printObject(data) return nil }) + type ConfigGetTypesOptions struct { + Robot string `json:"robot" choices:"yes|no|only"` + } + R(&ConfigGetTypesOptions{}, "notify-config-get-types", "Get all Config types", func(s *mcclient.ClientSession, args *ConfigGetTypesOptions) error { + param := jsonutils.Marshal(args) + result, err := modules.NotifyConfig.PerformClassAction(s, "get-types", param) + if err != nil { + return err + } + printObject(result) + return nil + }) } func configIdFromType(s *mcclient.ClientSession, t string) (string, error) { diff --git a/pkg/apis/notify/config.go b/pkg/apis/notify/config.go index 86efdd7d38..c56f7b261c 100644 --- a/pkg/apis/notify/config.go +++ b/pkg/apis/notify/config.go @@ -68,3 +68,14 @@ type ConfigValidateOutput struct { IsValid bool `json:"is_valid"` Message string `json:"message"` } + +type ConfigManagerGetTypesInput struct { + // description: Filter about robot + // enum: no,yes,only + // example: yes + Robot string `json:"robot"` +} + +type ConfigManagerGetTypesOutput struct { + Types []string `json:"types"` +} diff --git a/pkg/cloudcommon/notifyclient/notify.go b/pkg/cloudcommon/notifyclient/notify.go index e29625ca4f..b96cb924d3 100644 --- a/pkg/cloudcommon/notifyclient/notify.go +++ b/pkg/cloudcommon/notifyclient/notify.go @@ -142,6 +142,7 @@ func NotifyNormal(recipientId []string, isGroup bool, event string, data jsonuti notify.NotifyByDingTalk, notify.NotifyByWebConsole, notify.NotifyByFeishu, + notify.NotifyByWorkwx, } { RawNotify(recipientId, isGroup, c, @@ -157,6 +158,7 @@ func NotifyImportant(recipientId []string, isGroup bool, event string, data json notify.NotifyByMobile, notify.NotifyByWebConsole, notify.NotifyByFeishu, + notify.NotifyByWorkwx, } { RawNotify(recipientId, isGroup, c, @@ -172,6 +174,7 @@ func NotifyCritical(recipientId []string, isGroup bool, event string, data jsonu notify.NotifyByMobile, notify.NotifyByWebConsole, notify.NotifyByFeishu, + notify.NotifyByWorkwx, } { RawNotify(recipientId, isGroup, c, @@ -180,6 +183,32 @@ func NotifyCritical(recipientId []string, isGroup bool, event string, data jsonu } } +// NotifyAllWithoutRobot will send messages via all contacnt type from exclude robot contact type such as dingtalk-robot. +func NotifyAllWithoutRobot(recipientId []string, isGroup bool, priority notify.TNotifyPriority, event string, data jsonutils.JSONObject) error { + return notifyRobot("no", recipientId, isGroup, priority, event, data) +} + +// NotifyRobot will send messages via all robot contact type such as dingtalk-robot. +func NotifyRobot(recipientId []string, isGroup bool, priority notify.TNotifyPriority, event string, data jsonutils.JSONObject) error { + return notifyRobot("only", recipientId, isGroup, priority, event, data) +} + +func notifyRobot(robot string, recipientId []string, isGroup bool, priority notify.TNotifyPriority, event string, data jsonutils.JSONObject) error { + s := auth.GetAdminSession(context.Background(), consts.GetRegion(), "") + params := jsonutils.NewDict() + params.Set("robot", jsonutils.NewString(robot)) + result, err := modules.NotifyConfig.PerformClassAction(s, "get-types", params) + if err != nil { + return err + } + jarray, _ := result.Get("types") + cTypes := jarray.(*jsonutils.JSONArray).GetStringArray() + for _, ct := range cTypes { + RawNotify(recipientId, isGroup, notify.TNotifyChannel(ct), priority, event, data) + } + return nil +} + func SystemNotify(priority notify.TNotifyPriority, event string, data jsonutils.JSONObject) { // userId Notify(notifyAdminUsers, false, priority, event, data) diff --git a/pkg/mcclient/modules/notify/consts.go b/pkg/mcclient/modules/notify/consts.go index e973c926e8..456be0640d 100644 --- a/pkg/mcclient/modules/notify/consts.go +++ b/pkg/mcclient/modules/notify/consts.go @@ -28,4 +28,5 @@ const ( NotifyByDingTalk = TNotifyChannel("dingtalk") NotifyByWebConsole = TNotifyChannel("webconsole") NotifyByFeishu = TNotifyChannel("feishu") + NotifyByWorkwx = TNotifyChannel("workwx") ) diff --git a/pkg/notify/models/config.go b/pkg/notify/models/config.go index 33fb20dd21..a8d0f202cf 100644 --- a/pkg/notify/models/config.go +++ b/pkg/notify/models/config.go @@ -17,6 +17,7 @@ package models import ( "context" "database/sql" + "strings" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -132,6 +133,55 @@ func (c *SConfig) PostUpdate(ctx context.Context, userCred mcclient.TokenCredent NotifyService.RestartService(ctx, configMap, c.Type) } +func (cm *SConfigManager) AllowPerformGetTypes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (cm *SConfigManager) PerformGetTypes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ConfigManagerGetTypesInput) (api.ConfigManagerGetTypesOutput, error) { + output := api.ConfigManagerGetTypesOutput{} + allContactType, err := cm.allContactType() + if err != nil { + return output, err + } + var judge func(string) bool + switch input.Robot { + case "only": + judge = func(ctype string) bool { + return strings.Contains(ctype, "robot") + } + case "yes": + judge = func(ctype string) bool { + return true + } + default: + judge = func(ctype string) bool { + return !strings.Contains(ctype, "robot") + } + } + for _, ctype := range allContactType { + if judge(ctype) { + output.Types = append(output.Types, ctype) + } + } + return output, nil +} + +func (cm *SConfigManager) allContactType() ([]string, error) { + q := cm.Query("type") + allTypes := make([]struct { + Type string + }, 0, 3) + err := q.All(&allTypes) + if err != nil { + return nil, err + } + ret := make([]string, len(allTypes)) + for i := range ret { + ret[i] = allTypes[i].Type + } + return ret, nil +} + func (self *SConfigManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.ConfigListInput) (*sqlchemy.SQuery, error) { q, err := self.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput) if err != nil { diff --git a/pkg/notify/models/notification.go b/pkg/notify/models/notification.go index b10d1f0c66..97afc06ba2 100644 --- a/pkg/notify/models/notification.go +++ b/pkg/notify/models/notification.go @@ -24,6 +24,7 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/sets" + "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/notify" @@ -71,6 +72,14 @@ type SNotification struct { } func (nm *SNotificationManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.NotificationCreateInput) (api.NotificationCreateInput, error) { + // check contact type enabled + allContactType, err := ConfigManager.allContactType() + if err != nil { + return input, err + } + if !utils.IsInStringArray(input.ContactType, allContactType) { + return input, httperrors.NewInputParameterError("Unconfigured contact type %q", input.ContactType) + } // check uids, rids and contacts if len(input.Receivers) == 0 && len(input.Contacts) == 0 { return input, httperrors.NewMissingParameterError("receivers | contacts") diff --git a/pkg/notify/models/receiver.go b/pkg/notify/models/receiver.go index 8a3d635b5e..20848373f8 100644 --- a/pkg/notify/models/receiver.go +++ b/pkg/notify/models/receiver.go @@ -119,6 +119,9 @@ func (rm *SReceiverManager) InitializeData() error { if err != nil { return errors.Wrap(err, "db.FetchModelObjects") } + if len(contacts) == 0 { + return nil + } // build uid map uids := make([]string, 0, 10) @@ -186,6 +189,7 @@ func (rm *SReceiverManager) InitializeData() error { webconsole = true subContact.Contact = uid } else { + subContact.ParentContactType = api.MOBILE subContact.Contact = contact.Contact } subContact.ReceiverID = uid @@ -338,11 +342,15 @@ func (r *SReceiver) setEnabledContactType(contactType string, enabled bool) { if sc, ok := r.subContactCache[contactType]; ok { sc.Enabled = tristate.NewFromBool(enabled) } else { - r.subContactCache[contactType] = &SSubContact{ + subContact := &SSubContact{ Type: contactType, ReceiverID: r.Id, Enabled: tristate.NewFromBool(enabled), } + if contactType != api.WEBCONSOLE { + subContact.ParentContactType = api.MOBILE + } + r.subContactCache[contactType] = subContact } } } @@ -369,10 +377,15 @@ func (r *SReceiver) MarkContactTypeVerified(contactType string) error { if sc, ok := r.subContactCache[contactType]; ok { sc.Verified = tristate.True } else { - r.subContactCache[contactType] = &SSubContact{ + subContact := &SSubContact{ + Type: contactType, ReceiverID: r.Id, - Verified: tristate.True, + Enabled: tristate.True, } + if contactType != api.WEBCONSOLE { + subContact.ParentContactType = api.MOBILE + } + r.subContactCache[contactType] = subContact } return nil } @@ -387,10 +400,15 @@ func (r *SReceiver) setVerifiedContactType(contactType string, enabled bool) { if sc, ok := r.subContactCache[contactType]; ok { sc.Verified = tristate.NewFromBool(enabled) } else { - r.subContactCache[contactType] = &SSubContact{ + subContact := &SSubContact{ + Type: contactType, ReceiverID: r.Id, Verified: tristate.NewFromBool(enabled), } + if contactType != api.WEBCONSOLE { + subContact.ParentContactType = api.MOBILE + } + r.subContactCache[contactType] = subContact } } } @@ -630,7 +648,7 @@ func (r *SReceiver) PreUpdate(ctx context.Context, userCred mcclient.TokenCreden if len(input.Email) != 0 && input.Email != r.Email { r.VerifiedEmail = tristate.False for _, c := range r.subContactCache { - if c.ParentContactType == input.Email { + if c.ParentContactType == api.EMAIL { c.Verified = tristate.False } } @@ -638,7 +656,7 @@ func (r *SReceiver) PreUpdate(ctx context.Context, userCred mcclient.TokenCreden if len(input.Mobile) != 0 && input.Mobile != r.Mobile { r.VerifiedMobile = tristate.False for _, c := range r.subContactCache { - if c.ParentContactType == input.Mobile { + if c.ParentContactType == api.MOBILE { c.Verified = tristate.False } }