// 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" "sync" "time" "yunion.io/x/jsonutils" "yunion.io/x/log" api "yunion.io/x/onecloud/pkg/apis/notify" "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules/identity" npk "yunion.io/x/onecloud/pkg/mcclient/modules/notify" "yunion.io/x/onecloud/pkg/util/stringutils2" ) var ( notifyClientWorkerMan *appsrv.SWorkerManager notifyAdminUsers []string notifyAdminGroups []string notifyDBHookResources sync.Map ) func init() { notifyClientWorkerMan = appsrv.NewWorkerManager("NotifyClientWorkerManager", 1, 1024, false) // set db notify hook db.SetUpdateNotifyHook(func(ctx context.Context, userCred mcclient.TokenCredential, obj db.IModel) { _, ok := notifyDBHookResources.Load(obj.KeywordPlural()) if !ok { return } EventNotify(ctx, userCred, SEventNotifyParam{ Obj: obj, Action: ActionUpdate, }) }) db.SetCustomizeNotifyHook(func(ctx context.Context, userCred mcclient.TokenCredential, action string, obj db.IModel, moreDetails jsonutils.JSONObject) { _, ok := notifyDBHookResources.Load(obj.KeywordPlural()) if !ok { return } EventNotify(ctx, userCred, SEventNotifyParam{ Obj: obj, Action: api.SAction(action), ObjDetailsDecorator: func(ctx context.Context, details *jsonutils.JSONDict) { if moreDetails != nil { details.Set("customize_details", moreDetails) } }, }) }) db.SetStatusChangedNotifyHook(func(ctx context.Context, userCred mcclient.TokenCredential, oldStatus, newStatus string, obj db.IModel) { _, ok := notifyDBHookResources.Load(obj.KeywordPlural()) if !ok { return } EventNotify(ctx, userCred, SEventNotifyParam{ Obj: obj, Action: api.ActionStatusChanged, ObjDetailsDecorator: func(ctx context.Context, details *jsonutils.JSONDict) { details.Set("old_status", jsonutils.NewString(oldStatus)) details.Set("new_status", jsonutils.NewString(newStatus)) }, }) }) } func AddNotifyDBHookResources(keywordPlurals ...string) { for _, kp := range keywordPlurals { notifyDBHookResources.Store(kp, true) } } func NotifyWithCtx(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { notify(ctx, recipientId, isGroup, priority, event, data) } func Notify(recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { notify(context.Background(), recipientId, isGroup, priority, event, data) } func NotifyWithTag(ctx context.Context, params SNotifyParams) { p := newSNotifyParams(params.Event, params.Data). withRecipientId(params.RecipientId). withIsGroup(params.IsGroup). withPriority(params.Priority). withTag(params.Tag). withMetadata(params.Metadata). withIgnoreNonexistentReceiver(params.IgnoreNonexistentReceiver) notifyWithChannel(ctx, p, npk.NotifyByEmail, npk.NotifyByMobile, npk.NotifyByDingTalk, npk.NotifyByFeishu, npk.NotifyByWorkwx, npk.NotifyByWebConsole, ) } type SNotifyParams struct { RecipientId []string IsGroup bool Priority npk.TNotifyPriority Event string Data jsonutils.JSONObject Tag string Metadata map[string]interface{} IgnoreNonexistentReceiver bool } func NotifyWithContact(ctx context.Context, contacts []string, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { p := newSNotifyParams(event, data). withContactChannelAndPriority(contacts, channel, priority) rawNotify(ctx, p) } func NotifyNormal(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { notifyNormal(context.Background(), recipientId, isGroup, event, data) } func NotifyNormalWithCtx(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { notifyNormal(ctx, recipientId, isGroup, event, data) } func NotifyImportant(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { notifyImportant(context.Background(), recipientId, isGroup, event, data) } func NotifyImportantWithCtx(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { notifyImportant(ctx, recipientId, isGroup, event, data) } func NotifyCritical(recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { notifyCritical(context.Background(), recipientId, isGroup, event, data) } func NotifyCriticalWithCtx(ctx context.Context, recipientId []string, isGroup bool, event string, data jsonutils.JSONObject) { notifyCritical(ctx, recipientId, isGroup, event, data) } // NotifyAllWithoutRobot will send messages via all contacnt type from exclude robot contact type such as dingtalk-robot. func NotifyAllWithoutRobot(recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { return notifyAll(context.Background(), recipientId, isGroup, priority, event, data) } // NotifyAllWithoutRobot will send messages via all contacnt type from exclude robot contact type such as dingtalk-robot. func NotifyAllWithoutRobotWithCtx(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { return NotifyAllWithoutRobotWithCtxAndTemplateFuncs(ctx, recipientId, isGroup, priority, event, data, nil) } // NotifyAllWithoutRobotWithCtxAndTemplateFuncs 发送通知给所有用户(不包括机器人),支持自定义模板函数 func NotifyAllWithoutRobotWithCtxAndTemplateFuncs(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) error { return notifyAllWithTemplateFuncs(ctx, recipientId, isGroup, priority, event, data, templateFuncs) } // NotifyRobot will send messages via all robot contact type such as dingtalk-robot. func NotifyRobot(robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { return NotifyRobotWithCtx(context.Background(), robotIds, priority, event, data) } // NotifyRobot will send messages via all robot contact type such as dingtalk-robot. func NotifyRobotWithCtx(ctx context.Context, robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { return NotifyRobotWithCtxAndTemplateFuncs(ctx, robotIds, priority, event, data, nil) } // NotifyRobotWithCtxAndTemplateFuncs 发送通知给机器人,支持自定义模板函数 func NotifyRobotWithCtxAndTemplateFuncs(ctx context.Context, robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) error { p := newSNotifyParams(event, data). withRobotChannelAndPriority(robotIds, npk.NotifyByRobot, priority, templateFuncs) rawNotify(ctx, p) return nil } func SystemNotify(priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { systemNotify(context.Background(), priority, event, data) } func SystemNotifyWithCtx(ctx context.Context, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { SystemNotifyWithCtxAndTemplateFuncs(ctx, priority, event, data, nil) } // SystemNotifyWithCtxAndTemplateFuncs 发送系统通知,支持自定义模板函数 func SystemNotifyWithCtxAndTemplateFuncs(ctx context.Context, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) { systemNotifyWithTemplateFuncs(ctx, priority, event, data, templateFuncs) } func NotifyGeneralSystemError(data jsonutils.JSONObject) { notifyGeneralSystemError(context.Background(), data) } func NotifyGeneralSystemErrorWithCtx(ctx context.Context, data jsonutils.JSONObject) { notifyGeneralSystemError(ctx, data) } type SSystemEventMsg struct { Id string Name string Event string Reason string Created time.Time } func NotifySystemError(idstr string, name string, event string, reason string) { notifySystemError(context.Background(), idstr, name, event, reason) } func NotifySystemErrorWithCtx(ctx context.Context, idstr string, name string, event string, reason string) { notifySystemError(ctx, idstr, name, event, reason) } func NotifyError(ctx context.Context, userCred mcclient.TokenCredential, idstr, name, event, reason string) { msg := SSystemEventMsg{ Id: idstr, Name: name, Event: event, Reason: reason, Created: time.Now(), } notify(ctx, []string{userCred.GetUserId()}, false, npk.NotifyPriorityCritical, SYSTEM_ERROR, jsonutils.Marshal(msg)) } func NotifySystemWarning(idstr string, name string, event string, reason string) { notifySystemWarning(context.Background(), idstr, name, event, reason) } func NotifySystemWarningWithCtx(ctx context.Context, idstr string, name string, event string, reason string) { notifySystemWarning(ctx, idstr, name, event, reason) } func NotifyWebhook(ctx context.Context, userCred mcclient.TokenCredential, obj db.IModel, action api.SAction) { ret, err := db.FetchCustomizeColumns(obj.GetModelManager(), ctx, userCred, jsonutils.NewDict(), []interface{}{obj}, stringutils2.SSortedStrings{}, false) if err != nil { log.Errorf("unable to NotifyWebhook: %v", err) return } if len(ret) == 0 { log.Errorf("unable to NotifyWebhook: details of model %q is empty", obj.GetId()) return } event := Event.WithAction(action).WithResourceType(obj.GetModelManager()) msg := jsonutils.NewDict() msg.Set("resource_type", jsonutils.NewString(event.ResourceType())) msg.Set("action", jsonutils.NewString(event.Action())) msg.Set("resource_details", ret[0]) RawNotifyWithCtx(ctx, []string{}, false, npk.NotifyByWebhook, npk.NotifyPriorityNormal, event.String(), msg) } type SEventMessage struct { ResourceType string `json:"resource_type"` Action string `json:"action"` ResourceDetails *jsonutils.JSONDict `json:"resource_details"` } type SEventNotifyParam struct { Obj db.IModel ResourceType string Action api.SAction IsFail bool ObjDetailsDecorator func(context.Context, *jsonutils.JSONDict) AdvanceDays int } type eventTask struct { params api.NotificationManagerEventNotifyInput } func (t *eventTask) Dump() string { return fmt.Sprintf("eventTask params: %v", t.params) } func (t *eventTask) Run() { s, err := AdminSessionGenerator(context.Background(), "") if err != nil { log.Errorf("unable to get admin session: %v", err) return } _, err = npk.Notification.PerformClassAction(s, "event-notify", jsonutils.Marshal(t.params)) if err != nil { log.Errorf("unable to EventNotify: %s", err) } } func EventNotify(ctx context.Context, userCred mcclient.TokenCredential, ep SEventNotifyParam) { var objDetails *jsonutils.JSONDict if ep.Action == ActionDelete || ep.Action == ActionSyncDelete { objDetails = jsonutils.Marshal(ep.Obj).(*jsonutils.JSONDict) } else { ret, err := db.FetchCustomizeColumns(ep.Obj.GetModelManager(), ctx, userCred, jsonutils.NewDict(), []interface{}{ep.Obj}, nil, false) if err != nil { log.Errorf("unable to FetchCustomizeColumns: %v", err) return } if len(ret) == 0 { log.Errorf("unable to FetchCustomizeColumns: details of model %q is empty", ep.Obj.GetId()) return } objDetails = ret[0] } if ep.ObjDetailsDecorator != nil { ep.ObjDetailsDecorator(ctx, objDetails) } rt := ep.ResourceType if len(rt) == 0 { rt = ep.Obj.GetModelManager().Keyword() } event := api.Event.WithAction(ep.Action).WithResourceType(rt) if ep.IsFail { event = event.WithResult(api.ResultFailed) } var ( projectId string projectDomainId string ) ownerId := ep.Obj.GetOwnerId() if ownerId != nil { projectId = ownerId.GetProjectId() projectDomainId = ownerId.GetProjectDomainId() } params := api.NotificationManagerEventNotifyInput{ ReceiverIds: []string{userCred.GetUserId()}, ResourceDetails: objDetails, Event: event.String(), AdvanceDays: ep.AdvanceDays, Priority: string(npk.NotifyPriorityNormal), ProjectId: projectId, ProjectDomainId: projectDomainId, ResourceType: ep.ResourceType, Action: ep.Action, } EventNotify2(params) } func EventNotify2(params api.NotificationManagerEventNotifyInput) { t := eventTask{ params: params, } notifyClientWorkerMan.Run(&t, nil, nil) } func EventNotifyServiceAbnormal(ctx context.Context, userCred mcclient.TokenCredential, service, method, path string, body jsonutils.JSONObject, err error) { event := api.Event.WithAction(api.ActionServiceAbnormal).WithResourceType(api.TOPIC_RESOURCE_SERVICE) obj := jsonutils.NewDict() if body != nil { obj.Set("body", jsonutils.NewString(body.PrettyString())) } obj.Set("method", jsonutils.NewString(method)) obj.Set("path", jsonutils.NewString(path)) obj.Set("error", jsonutils.NewString(err.Error())) obj.Set("service_name", jsonutils.NewString(service)) params := api.NotificationManagerEventNotifyInput{ ReceiverIds: []string{userCred.GetUserId()}, ResourceDetails: obj, Event: event.String(), AdvanceDays: 0, Priority: string(npk.NotifyPriorityNormal), ResourceType: api.TOPIC_RESOURCE_SERVICE, Action: api.ActionServiceAbnormal, } EventNotify2(params) } func systemEventNotify(ctx context.Context, action api.SAction, resType string, result api.SResult, priority string, obj *jsonutils.JSONDict) { event := api.Event.WithAction(action).WithResourceType(resType).WithResult(result) params := api.NotificationManagerEventNotifyInput{ ReceiverIds: []string{}, ResourceDetails: obj, Event: event.String(), Priority: priority, } EventNotify2(params) } func SystemEventNotify(ctx context.Context, action api.SAction, resType string, obj *jsonutils.JSONDict) { systemEventNotify(ctx, action, resType, api.ResultSucceed, string(npk.NotifyPriorityNormal), obj) } func SystemExceptionNotify(ctx context.Context, action api.SAction, resType string, obj *jsonutils.JSONDict) { systemEventNotify(ctx, action, resType, api.ResultSucceed, string(npk.NotifyPriorityCritical), obj) } func SystemExceptionNotifyWithResult(ctx context.Context, action api.SAction, resType string, result api.SResult, obj *jsonutils.JSONDict) { systemEventNotify(ctx, action, resType, result, string(npk.NotifyPriorityCritical), obj) } func RawNotifyWithCtx(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { RawNotifyWithCtxAndTemplateFuncs(ctx, recipientId, isGroup, channel, priority, event, data, nil) } // RawNotifyWithCtxAndTemplateFuncs 发送通知,支持自定义模板函数 func RawNotifyWithCtxAndTemplateFuncs(ctx context.Context, recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject, templateFuncs template.FuncMap) { p := newSNotifyParams(event, data). withRecipientChannelAndPriority(recipientId, isGroup, channel, priority, templateFuncs) rawNotify(ctx, p) } func RawNotify(recipientId []string, isGroup bool, channel npk.TNotifyChannel, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { p := newSNotifyParams(event, data). withRecipientChannelAndPriority(recipientId, isGroup, channel, priority, nil) rawNotify(context.Background(), p) } // 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) { p := newSNotifyParams(event, data). withRecipientChannelAndPriority(recipientId, isGroup, channel, priority, nil). withCreateReceiver(createReceiver) intelliNotify(ctx, p) } func FetchNotifyAdminRecipients(ctx context.Context, region string, users []string, groups []string) { s, err := AdminSessionGenerator(ctx, region) if err != nil { log.Errorf("unable to get admin session: %v", err) } notifyAdminUsers = make([]string, 0) for _, u := range users { uId, err := getIdentityId(s, u, &identity.UsersV3) if err != nil { log.Warningf("fetch user %s fail: %s", u, err) } else { notifyAdminUsers = append(notifyAdminUsers, uId) } } notifyAdminGroups = make([]string, 0) for _, g := range groups { gId, err := getIdentityId(s, g, &identity.Groups) if err != nil { log.Warningf("fetch group %s fail: %s", g, err) } else { notifyAdminGroups = append(notifyAdminGroups, gId) } } } func NotifyVmIntegrity(ctx context.Context, name string) { data := jsonutils.NewDict() data.Add(jsonutils.NewString(name), "name") SystemExceptionNotifyWithResult(ctx, api.ActionChecksumTest, api.TOPIC_RESOURCE_VM_INTEGRITY_CHECK, api.ResultFailed, data) }