| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package notifyclient
- import (
- "context"
- "fmt"
- "html/template"
- "regexp"
- "strings"
- "time"
- "golang.org/x/text/language"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/appctx"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/httputils"
- "yunion.io/x/pkg/util/sets"
- "yunion.io/x/onecloud/pkg/cloudcommon/consts"
- "yunion.io/x/onecloud/pkg/mcclient"
- "yunion.io/x/onecloud/pkg/mcclient/modulebase"
- "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
- npk "yunion.io/x/onecloud/pkg/mcclient/modules/notify"
- )
- func notifySystemWarning(ctx context.Context, idstr string, name string, event string, reason string) {
- msg := SSystemEventMsg{
- Id: idstr,
- Name: name,
- Event: event,
- Reason: reason,
- Created: time.Now(),
- }
- systemNotify(ctx, npk.NotifyPriorityImportant, SYSTEM_WARNING, jsonutils.Marshal(msg))
- }
- func notifyGeneralSystemError(ctx context.Context, data jsonutils.JSONObject) {
- systemNotify(ctx, npk.NotifyPriorityCritical, SYSTEM_ERROR, data)
- }
- func notifySystemError(ctx context.Context, idstr string, name string, event string, reason string) {
- msg := SSystemEventMsg{
- Id: idstr,
- Name: name,
- Event: event,
- Reason: reason,
- Created: time.Now(),
- }
- systemNotify(ctx, npk.NotifyPriorityCritical, SYSTEM_ERROR, jsonutils.Marshal(msg))
- }
- func systemNotify(ctx context.Context, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
- systemNotifyWithTemplateFuncs(ctx, priority, event, data, nil)
- }
- func systemNotifyWithTemplateFuncs(ctx context.Context, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) {
- // userId
- notifyWithTemplateFuncs(ctx, notifyAdminUsers, false, priority, event, data, templateFuncs)
- // groupId
- notifyWithTemplateFuncs(ctx, notifyAdminGroups, true, priority, event, data, templateFuncs)
- }
- func notifyAll(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error {
- return notifyAllWithTemplateFuncs(ctx, recipientId, isGroup, priority, event, data, nil)
- }
- func notifyAllWithTemplateFuncs(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) error {
- s, err := AdminSessionGenerator(ctx, consts.GetRegion())
- if err != nil {
- return err
- }
- params := jsonutils.NewDict()
- result, err := npk.NotifyReceiver.PerformClassAction(s, "get-types", params)
- if err != nil {
- return err
- }
- if result == nil {
- return nil
- }
- jarray, err := result.Get("types")
- if err != nil {
- return err
- }
- cTypes := jarray.(*jsonutils.JSONArray).GetStringArray()
- for _, ct := range cTypes {
- RawNotifyWithCtxAndTemplateFuncs(ctx, recipientId, isGroup, npk.TNotifyChannel(ct), priority, event, data, templateFuncs)
- }
- return nil
- }
- type sTarget struct {
- reIds []string
- contacts []string
- robots []string
- }
- func langRobot(ctx context.Context, robots []string) (map[language.Tag]*sTarget, error) {
- contextLang := appctx.Lang(ctx)
- robotLang, err := getRobotLang(robots)
- if err != nil {
- return nil, err
- }
- ret := make(map[language.Tag]*sTarget)
- for robot, langStr := range robotLang {
- lang, err := language.Parse(langStr)
- if err != nil {
- log.Errorf("can't parse %s to language.Tag: %v", langStr, err)
- lang = contextLang
- }
- t := ret[lang]
- if t == nil {
- ret[lang] = &sTarget{}
- t = ret[lang]
- }
- t.robots = append(t.robots, robot)
- }
- return ret, nil
- }
- func lang(ctx context.Context, contactType npk.TNotifyChannel, reIds []string, contacts []string) (map[language.Tag]*sTarget, error) {
- contextLang := appctx.Lang(ctx)
- langMap := make(map[language.Tag]*sTarget)
- insertReid := func(lang language.Tag, id string) {
- t := langMap[lang]
- if t == nil {
- t = &sTarget{}
- langMap[lang] = t
- }
- t.reIds = append(t.reIds, id)
- }
- insertContact := func(lang language.Tag, id string) {
- t := langMap[lang]
- if t == nil {
- t = &sTarget{}
- langMap[lang] = t
- }
- t.contacts = append(t.contacts, id)
- }
- uids := append([]string{}, reIds...)
- if contactType == npk.NotifyByWebConsole {
- uids = append(uids, contacts...)
- }
- uidLang, err := UserLangFetcher(uids)
- if err != nil {
- return nil, errors.Wrap(err, "unable to feth UserLang")
- }
- insert := func(id string, insertFunc func(language.Tag, string)) {
- langStr := uidLang[id]
- if len(langStr) == 0 {
- insertFunc(contextLang, id)
- return
- }
- lang, err := language.Parse(langStr)
- if err != nil {
- log.Errorf("can't parse %s to language.Tag: %v", langStr, err)
- insertFunc(contextLang, id)
- return
- }
- insertFunc(lang, id)
- }
- for _, reid := range reIds {
- insert(reid, insertReid)
- }
- if contactType == npk.NotifyByWebConsole {
- for _, contact := range contacts {
- insert(contact, insertContact)
- }
- } else {
- for _, cs := range contacts {
- insertContact(contextLang, cs)
- }
- }
- return langMap, nil
- }
- func isEmptyRecipients(recipientId, robots []string) bool {
- var recvs []string
- for _, c := range recipientId {
- if len(c) > 0 {
- recvs = append(recvs, c)
- }
- }
- for _, robot := range robots {
- if len(robot) > 0 {
- recvs = append(recvs, robot)
- }
- }
- return len(recvs) == 0
- }
- func genMsgViaLang(ctx context.Context, p sNotifyParams) ([]npk.SNotifyMessage, error) {
- reIds := make([]string, 0)
- s, err := AdminSessionGenerator(context.Background(), consts.GetRegion())
- if err != nil {
- return nil, err
- }
- if p.isGroup {
- // fetch uid
- uidSet := sets.NewString()
- for _, gid := range p.recipientId {
- users, err := identity.Groups.GetUsers(s, gid, nil)
- if err != nil {
- return nil, errors.Wrapf(err, "Groups.GetUsers for group %q", gid)
- }
- for i := range users.Data {
- id, _ := users.Data[i].GetString("id")
- uidSet.Insert(id)
- }
- }
- reIds = append(reIds, uidSet.UnsortedList()...)
- } else {
- reIds = p.recipientId
- }
- if isEmptyRecipients(p.recipientId, p.robots) {
- return nil, errors.Wrap(errors.ErrEmpty, "empty receipients")
- }
- if !hasTemplateOfTopic(p.event) {
- msg := npk.SNotifyMessage{}
- msg.Uid = reIds
- msg.Priority = p.priority
- msg.Robots = p.robots
- msg.Contacts = p.contacts
- msg.ContactType = p.channel
- msg.Topic = p.event
- msg.Msg = p.data.String()
- msg.Tag = p.tag
- msg.Metadata = p.metadata
- msg.IgnoreNonexistentReceiver = p.ignoreNonexistentReceiver
- return []npk.SNotifyMessage{msg}, nil
- }
- var langMap map[language.Tag]*sTarget
- if p.channel == npk.NotifyByRobot {
- langMap, err = langRobot(ctx, p.robots)
- if err != nil {
- return nil, err
- }
- } else {
- langMap, err = lang(ctx, p.channel, reIds, p.contacts)
- if err != nil {
- return nil, err
- }
- }
- msgs := make([]npk.SNotifyMessage, 0, len(langMap))
- for lang, t := range langMap {
- langSuffix := notifyclientI18nTable.LookupByLang(lang, suffix)
- msg := npk.SNotifyMessage{}
- msg.Uid = t.reIds
- msg.Priority = p.priority
- msg.Robots = p.robots
- msg.Contacts = t.contacts
- msg.ContactType = p.channel
- topic, err := getContent(langSuffix, p.event, "title", p.channel, p.data, p.templateFuncs)
- if err != nil {
- log.Warningf("get title error: %s", err)
- }
- if len(topic) == 0 {
- topic = p.event
- }
- msg.Topic = topic
- body, err := getContent(langSuffix, p.event, "content", p.channel, p.data, p.templateFuncs)
- if err != nil {
- log.Errorf("get content error: %s", err)
- }
- if len(body) == 0 {
- body, _ = p.data.GetString()
- }
- msg.Msg = body
- msg.Tag = p.tag
- msg.Metadata = p.metadata
- msg.IgnoreNonexistentReceiver = p.ignoreNonexistentReceiver
- msgs = append(msgs, msg)
- }
- return msgs, nil
- }
- type sNotifyParams struct {
- recipientId []string
- robots []string
- isGroup bool
- contacts []string
- channel npk.TNotifyChannel
- priority npk.TNotifyPriority
- event string
- data jsonutils.JSONObject
- createReceiver bool
- tag string
- metadata map[string]interface{}
- ignoreNonexistentReceiver bool
- templateFuncs template.FuncMap
- }
- // newSNotifyParams 创建 sNotifyParams 结构体,event 和 data 是必需参数
- func newSNotifyParams(event string, data jsonutils.JSONObject) sNotifyParams {
- return sNotifyParams{
- event: event,
- data: data,
- }
- }
- // withRecipientAndPriority 设置 recipientId、isGroup、priority 和 templateFuncs(用于 notifyCritical/Important/Normal)
- func (p sNotifyParams) withRecipientAndPriority(recipientId []string, isGroup bool, priority npk.TNotifyPriority, templateFuncs template.FuncMap) sNotifyParams {
- p.recipientId = recipientId
- p.isGroup = isGroup
- p.priority = priority
- p.templateFuncs = templateFuncs
- return p
- }
- // withRecipientChannelAndPriority 设置 recipientId、isGroup、channel、priority 和 templateFuncs(用于 RawNotify)
- func (p sNotifyParams) withRecipientChannelAndPriority(recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, templateFuncs template.FuncMap) sNotifyParams {
- p.recipientId = recipientId
- p.isGroup = isGroup
- p.channel = channel
- p.priority = priority
- p.templateFuncs = templateFuncs
- return p
- }
- // withRobotChannelAndPriority 设置 robots、channel、priority 和 templateFuncs(用于 NotifyRobot)
- func (p sNotifyParams) withRobotChannelAndPriority(robots []string, channel npk.TNotifyChannel, priority npk.TNotifyPriority, templateFuncs template.FuncMap) sNotifyParams {
- p.robots = robots
- p.channel = channel
- p.priority = priority
- p.templateFuncs = templateFuncs
- return p
- }
- // withContactChannelAndPriority 设置 contacts、channel 和 priority(用于 NotifyWithContact)
- func (p sNotifyParams) withContactChannelAndPriority(contacts []string, channel npk.TNotifyChannel, priority npk.TNotifyPriority) sNotifyParams {
- p.contacts = contacts
- p.channel = channel
- p.priority = priority
- return p
- }
- // withRecipientId 设置 recipientId
- func (p sNotifyParams) withRecipientId(recipientId []string) sNotifyParams {
- p.recipientId = recipientId
- return p
- }
- // withRobots 设置 robots
- func (p sNotifyParams) withRobots(robots []string) sNotifyParams {
- p.robots = robots
- return p
- }
- // withIsGroup 设置 isGroup
- func (p sNotifyParams) withIsGroup(isGroup bool) sNotifyParams {
- p.isGroup = isGroup
- return p
- }
- // withContacts 设置 contacts
- func (p sNotifyParams) withContacts(contacts []string) sNotifyParams {
- p.contacts = contacts
- return p
- }
- // withChannel 设置 channel
- func (p sNotifyParams) withChannel(channel npk.TNotifyChannel) sNotifyParams {
- p.channel = channel
- return p
- }
- // withPriority 设置 priority
- func (p sNotifyParams) withPriority(priority npk.TNotifyPriority) sNotifyParams {
- p.priority = priority
- return p
- }
- // withCreateReceiver 设置 createReceiver
- func (p sNotifyParams) withCreateReceiver(createReceiver bool) sNotifyParams {
- p.createReceiver = createReceiver
- return p
- }
- // withTag 设置 tag
- func (p sNotifyParams) withTag(tag string) sNotifyParams {
- p.tag = tag
- return p
- }
- // withMetadata 设置 metadata
- func (p sNotifyParams) withMetadata(metadata map[string]interface{}) sNotifyParams {
- p.metadata = metadata
- return p
- }
- // withIgnoreNonexistentReceiver 设置 ignoreNonexistentReceiver
- func (p sNotifyParams) withIgnoreNonexistentReceiver(ignoreNonexistentReceiver bool) sNotifyParams {
- p.ignoreNonexistentReceiver = ignoreNonexistentReceiver
- return p
- }
- // withTemplateFuncs 设置 templateFuncs
- func (p sNotifyParams) withTemplateFuncs(templateFuncs template.FuncMap) sNotifyParams {
- p.templateFuncs = templateFuncs
- return p
- }
- func rawNotify(ctx context.Context, p sNotifyParams) {
- intelliNotify(ctx, p)
- }
- func notifyCritical(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
- notifyCriticalWithTemplateFuncs(ctx, recipientId, isGroup, event, data, nil)
- }
- func notifyCriticalWithTemplateFuncs(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) {
- p := newSNotifyParams(event, data).
- withRecipientAndPriority(recipientId, isGroup, npk.NotifyPriorityNormal, templateFuncs)
- notifyWithChannel(ctx, p,
- npk.NotifyByEmail,
- npk.NotifyByDingTalk,
- npk.NotifyByMobile,
- npk.NotifyByWebConsole,
- npk.NotifyByFeishu,
- npk.NotifyByWorkwx,
- )
- }
- func notifyImportant(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
- notifyImportantWithTemplateFuncs(ctx, recipientId, isGroup, event, data, nil)
- }
- func notifyImportantWithTemplateFuncs(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) {
- p := newSNotifyParams(event, data).
- withRecipientAndPriority(recipientId, isGroup, npk.NotifyPriorityNormal, templateFuncs)
- notifyWithChannel(ctx, p,
- npk.NotifyByEmail,
- npk.NotifyByDingTalk,
- npk.NotifyByMobile,
- npk.NotifyByWebConsole,
- npk.NotifyByFeishu,
- npk.NotifyByWorkwx,
- )
- }
- func notifyNormal(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) {
- notifyNormalWithTemplateFuncs(ctx, recipientId, isGroup, event, data, nil)
- }
- func notifyNormalWithTemplateFuncs(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) {
- p := newSNotifyParams(event, data).
- withRecipientAndPriority(recipientId, isGroup, npk.NotifyPriorityNormal, templateFuncs)
- notifyWithChannel(ctx, p,
- npk.NotifyByEmail,
- npk.NotifyByDingTalk,
- npk.NotifyByFeishu,
- npk.NotifyByWorkwx,
- npk.NotifyByWebConsole,
- )
- }
- func notifyWithChannel(ctx context.Context, p sNotifyParams, channels ...npk.TNotifyChannel) {
- for _, c := range channels {
- np := p
- np.channel = c
- rawNotify(ctx, np)
- }
- }
- const noSuchReceiver = `no such receiver whose uid is '(.*)'`
- var noSuchReceiverRegexp = regexp.MustCompile(noSuchReceiver)
- type notifyTask struct {
- ctx context.Context
- msg npk.SNotifyMessage
- createReceiver bool
- }
- func (t *notifyTask) Dump() string {
- return fmt.Sprintf("msg: %v createReceiver: %v", t.msg, t.createReceiver)
- }
- func (t *notifyTask) Run() {
- s, err := AdminSessionGenerator(t.ctx, consts.GetRegion())
- if err != nil {
- log.Errorf("fail to get session: %v", err)
- }
- for {
- err := npk.Notifications.Send(s, t.msg)
- if err == nil {
- break
- }
- if !t.createReceiver {
- log.Errorf("unable to send notification to contacts %s uid %s gid %s @%s: %s", strings.Join(t.msg.Contacts, ","), strings.Join(t.msg.Uid, ","), strings.Join(t.msg.Gid, ","), t.msg.ContactType, jsonutils.Marshal(err))
- break
- }
- jerr, ok := err.(*httputils.JSONClientError)
- if !ok {
- log.Errorf("unable to send notification: %v", err)
- break
- }
- if jerr.Code > 500 {
- log.Errorf("unable to send notification: %v", err)
- break
- }
- match := noSuchReceiverRegexp.FindStringSubmatch(jerr.Details)
- if match == nil || len(match) <= 1 {
- log.Errorf("unable to send notification: %v", err)
- break
- }
- receiverId := match[1]
- createData := jsonutils.NewDict()
- createData.Set("uid", jsonutils.NewString(receiverId))
- _, err = npk.NotifyReceiver.Create(s, createData)
- if err != nil {
- log.Errorf("try to create receiver %q, but failed: %v", receiverId, err)
- break
- }
- log.Infof("create receiver %q successfully", receiverId)
- }
- }
- func intelliNotify(ctx context.Context, p sNotifyParams) {
- if isEmptyRecipients(p.recipientId, p.robots) {
- return
- }
- log.Infof("recipientId: %v, robots: %v, contacts: %v, event %s priority %s", p.recipientId, p.robots, p.contacts, p.event, p.priority)
- msgs, err := genMsgViaLang(ctx, p)
- if err != nil {
- log.Errorf("unable send notification: %v", err)
- }
- for i := range msgs {
- t := notifyTask{
- ctx: ctx,
- createReceiver: p.createReceiver,
- msg: msgs[i],
- }
- notifyClientWorkerMan.Run(&t, nil, nil)
- }
- // log.Debugf("send notification %s %s", topic, body)
- }
- func notify(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) {
- notifyWithTemplateFuncs(ctx, recipientId, isGroup, priority, event, data, nil)
- }
- func notifyWithTemplateFuncs(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) {
- switch priority {
- case npk.NotifyPriorityCritical:
- notifyCriticalWithTemplateFuncs(ctx, recipientId, isGroup, event, data, templateFuncs)
- case npk.NotifyPriorityImportant:
- notifyImportantWithTemplateFuncs(ctx, recipientId, isGroup, event, data, templateFuncs)
- default:
- notifyNormalWithTemplateFuncs(ctx, recipientId, isGroup, event, data, templateFuncs)
- }
- }
- func parseIdName(idName string) (string, string) {
- pos := strings.Index(idName, "\\")
- if pos > 0 {
- return idName[:pos], idName[pos+1:]
- } else {
- return "", idName
- }
- }
- var (
- domainCache = make(map[string]string)
- )
- func getIdentityId(s *mcclient.ClientSession, idName string, manager modulebase.Manager) (string, error) {
- domain, idName := parseIdName(idName)
- query := jsonutils.NewDict()
- if len(domain) > 0 {
- domainId, ok := domainCache[domain]
- if !ok {
- var err error
- domainId, err = identity.Domains.GetId(s, domain, nil)
- if err != nil {
- log.Errorf("fail to find domainId for domain %s: %s", domain, err)
- return "", err
- }
- domainCache[domain] = domainId
- }
- query.Add(jsonutils.NewString(domainId), "domain_id")
- }
- return manager.GetId(s, idName, query)
- }
|