diff --git a/build/notify/root/opt/yunion/share/template/content/VERIFY.email b/build/notify/root/opt/yunion/share/template/content/VERIFY.email index 612bd83941..042ae56740 100644 --- a/build/notify/root/opt/yunion/share/template/content/VERIFY.email +++ b/build/notify/root/opt/yunion/share/template/content/VERIFY.email @@ -47,16 +47,13 @@ - 请点击下面的链接,完成用户邮箱的激活: - - - {{.link}} + 您正在验证邮箱,请在验证码输入框中输入:{{.code}},已完成验证。 - *为了确保您的帐号安全,该链接仅48小时内访问有效,请勿直接回复此邮件。 + 如非本人操作,请及时登录平台并修改密码以保证账户的安全 版权所有 © {{.copyright}} 保留一切权利 diff --git a/cmd/climc/main.go b/cmd/climc/main.go index 6711336e78..89226423b8 100644 --- a/cmd/climc/main.go +++ b/cmd/climc/main.go @@ -32,7 +32,7 @@ import ( _ "yunion.io/x/onecloud/cmd/climc/shell/meter" _ "yunion.io/x/onecloud/cmd/climc/shell/misc" _ "yunion.io/x/onecloud/cmd/climc/shell/monitor" - _ "yunion.io/x/onecloud/cmd/climc/shell/notify" + _ "yunion.io/x/onecloud/cmd/climc/shell/notifyv2" _ "yunion.io/x/onecloud/cmd/climc/shell/servicetree" _ "yunion.io/x/onecloud/cmd/climc/shell/yunionconf" ) diff --git a/cmd/climc/shell/notify/contactgroups.go b/cmd/climc/shell/notify/contactgroups.go deleted file mode 100644 index bd057befd8..0000000000 --- a/cmd/climc/shell/notify/contactgroups.go +++ /dev/null @@ -1,53 +0,0 @@ -// 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 notify - -import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" -) - -func init() { - - /** - * 获得一个全部通信地址组 - */ - type ContactGroupsListOptions struct { - options.BaseListOptions - } - R(&ContactGroupsListOptions{}, "contact-group-list", "List all contact groups for all the domainsconta", func(s *mcclient.ClientSession, args *ContactGroupsListOptions) error { - var params *jsonutils.JSONDict - { - var err error - params, err = args.BaseListOptions.Params() - if err != nil { - return err - - } - } - - result, err := modules.ContactGroups.List(s, params) - if err != nil { - return err - } - - printList(result, modules.ContactGroups.GetColumns(s)) - return nil - }) - -} diff --git a/cmd/climc/shell/notify/contacts.go b/cmd/climc/shell/notify/contacts.go deleted file mode 100644 index d4d79a1045..0000000000 --- a/cmd/climc/shell/notify/contacts.go +++ /dev/null @@ -1,218 +0,0 @@ -// 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 notify - -import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modulebase" - "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" -) - -func init() { - - /** - * 操作用户的通信地址(如果用户的通信地址不存在则进行添加;如果已存在则进行修改;如果设置空则进行删除。) - */ - type ContactsUpdateOptions struct { - UID string `help:"The user you wanna add contact to (Keystone User ID)"` - CONTACTTYPE string `help:"The contact type email|mobile" choices:"email|mobile|dingtalk"` - CONTACT string `help:"The contacts details mobile number or email address or dingtalk's userid, if set it the empty str means delete"` - Status string `help:"Enabled or disabled contact status" choices:"enable|disable"` - Pull []string `help:"pull some subcontacts(e.g., dingtalk, feishu, etc) related to mobile"` - } - R(&ContactsUpdateOptions{}, "contact-update", "Create, delete or update contact for user", func(s *mcclient.ClientSession, args *ContactsUpdateOptions) error { - arr := jsonutils.NewArray() - tmpObj := jsonutils.NewDict() - tmpObj.Add(jsonutils.NewString(args.CONTACTTYPE), "contact_type") - tmpObj.Add(jsonutils.NewString(args.CONTACT), "contact") - if len(args.Status) > 0 { - if args.Status == "disable" { - tmpObj.Add(jsonutils.NewInt(0), "enabled") - } else { - tmpObj.Add(jsonutils.NewInt(1), "enabled") - } - } - - arr.Add(tmpObj) - - params := jsonutils.NewDict() - params.Add(arr, "contacts") - - pulls := jsonutils.NewArray() - for _, pull := range args.Pull { - pulls.Add(jsonutils.NewString(pull)) - } - params.Add(pulls, "pull") - - contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", params) - if err != nil { - return err - } - - printObject(contact) - return nil - }) - - type ContactsPullOptions struct { - UID string `help:"The user you wanna pull contact"` - CONTACTTYPE string `help:"The contact type"` - } - R(&ContactsPullOptions{}, "contact-pull", "Pull contact", func(s *mcclient.ClientSession, args *ContactsPullOptions) error { - params := jsonutils.NewDict() - params.Set("contacts", jsonutils.NewArray()) - params.Set("pull", jsonutils.NewArray(jsonutils.NewString(args.CONTACTTYPE))) - contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", params) - if err != nil { - return err - } - printObject(contact) - return nil - }) - - type ContactsDeleteOptions struct { - UID string `help:"The user you wanna add contact to (Keystone User ID)"` - CONTACTTYPE string `help:"The contact type email|mobile|dingtalk" choices:"email|mobile|dingtalk"` - } - R(&ContactsDeleteOptions{}, "contact-delete", "Delete contact for user", func(s *mcclient.ClientSession, args *ContactsDeleteOptions) error { - arr := jsonutils.NewArray() - tmpObj := jsonutils.NewDict() - tmpObj.Add(jsonutils.NewString(args.CONTACTTYPE), "contact_type") - tmpObj.Add(jsonutils.NewString(""), "contact") - arr.Add(tmpObj) - params := jsonutils.NewDict() - params.Add(arr, "contacts") - contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", params) - if err != nil { - return err - } - printObject(contact) - return nil - }) - - /** - * 获得所有用户的所有通信地址列表 - */ - type ContactsListOptions struct { - options.BaseListOptions - } - R(&ContactsListOptions{}, "contact-list", "List all contacts for all users", func(s *mcclient.ClientSession, args *ContactsListOptions) error { - var params *jsonutils.JSONDict - { - var err error - params, err = args.BaseListOptions.Params() - if err != nil { - return err - - } - } - params.Add(jsonutils.JSONTrue, "details") - - result, err := modules.Contacts.List(s, params) - if err != nil { - return err - } - - printList(result, modules.Contacts.GetColumns(s)) - return nil - }) - - /** - * 获得一个用户全部通信地址 - */ - type ContactsListForUserOptions struct { - options.BaseListOptions - UID string `help:"The user you wanna find contact from (Keystone User ID)"` - } - R(&ContactsListForUserOptions{}, "contact-show", "List all contacts for the users", func(s *mcclient.ClientSession, args *ContactsListForUserOptions) error { - var params *jsonutils.JSONDict - { - var err error - params, err = args.BaseListOptions.Params() - if err != nil { - return err - - } - } - - params.Add(jsonutils.JSONTrue, "details") - - result, err := modules.Contacts.CustomizedGet(s, args.UID, params) - if err != nil { - return err - } - - contactsStr, err := result.GetString("details") - if err != nil { - return nil - } - - contactsJson, err := jsonutils.ParseString(contactsStr) - if err != nil { - return err - } - - contacts, err := contactsJson.GetArray() - if err != nil { - return err - } - - printList(&modulebase.ListResult{Data: contacts}, nil) - return nil - }) - - /** - * 触发验证通信地址操作 - */ - type ContactsVerifyOptions struct { - UID string `help:"The user you wanna verify contact for (Keystone User ID)"` - CONTACT_TYPE string `help:"The contact type email|mobile"` - CONTACT string `help:"The contacts details mobile number or email address"` - } - R(&ContactsVerifyOptions{}, "contact-verify-trigger", "Trigger contact verify", func(s *mcclient.ClientSession, args *ContactsVerifyOptions) error { - tmpDict := jsonutils.NewDict() - tmpDict.Add(jsonutils.NewString(args.CONTACT_TYPE), "contact_type") - tmpDict.Add(jsonutils.NewString(args.CONTACT), "contact") - _, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "verify", tmpDict) - if err != nil { - return err - } - return nil - }) - - type ContactsBatchDeleteOptions struct { - UIDS []string `help:"All user'id you wanna to delete contacts (Keystone User ID)"` - } - R(&ContactsBatchDeleteOptions{}, "contact-delete", "Delete all contacts for the user", func(s *mcclient.ClientSession, args *ContactsBatchDeleteOptions) error { - arr := jsonutils.NewArray() - for _, f := range args.UIDS { - arr.Add(jsonutils.NewString(f)) - } - - params := jsonutils.NewDict() - params.Add(arr, "contacts") - - contact, err := modules.Contacts.DoBatchDeleteContacts(s, params) - - if err != nil { - return err - } - - printObject(contact) - return nil - }) -} diff --git a/cmd/climc/shell/notify/email_config.go b/cmd/climc/shell/notify/email_config.go deleted file mode 100644 index 5464b88bdc..0000000000 --- a/cmd/climc/shell/notify/email_config.go +++ /dev/null @@ -1,112 +0,0 @@ -// 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 notify - -import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modules" -) - -func init() { - - /** - * 查询邮件配置信息 - */ - type EmailConfigShowOptions struct { - TYPE string `help:"type "` - } - R(&EmailConfigShowOptions{}, "email-config-show", "Show email-config details", - func(s *mcclient.ClientSession, args *EmailConfigShowOptions) error { - result, err := modules.EmailConfigs.Get(s, args.TYPE, nil) - if err != nil { - return err - } - printObject(result) - return nil - }) - - /** - * 增加邮件配置信息 - */ - type EmailConfigCreateOptions struct { - USERNAME string `help:"Username for email sender"` - PASSWORD string `help:"Password for email sender"` - HOSTNAME string `help:"Email server name"` - SSLGLOBAL string `help:"use ssl_global"` - HOSTPORT int64 `help:"Email server port"` - } - - R(&EmailConfigCreateOptions{}, "email-config-create", "Create a Email Config", - func(s *mcclient.ClientSession, args *EmailConfigCreateOptions) error { - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.USERNAME), "username") - params.Add(jsonutils.NewString(args.PASSWORD), "password") - params.Add(jsonutils.NewString(args.HOSTNAME), "hostname") - params.Add(jsonutils.NewString(args.SSLGLOBAL), "ssl_global") - params.Add(jsonutils.NewInt(args.HOSTPORT), "hostport") - - result, err := modules.EmailConfigs.Create(s, params) - if err != nil { - return err - } - printObject(result) - return nil - }) - - /** - * 修改 - */ - type EmailConfigUpdateOptions struct { - TYPE string `help:"type of email "` - USERNAME string `help:"Username for email sender"` - PASSWORD string `help:"Password for email sender"` - HOSTNAME string `help:"Email server name"` - SSLGLOBAL string `help:"use ssl_global"` - HOSTPORT int64 `help:"Email server port"` - } - R(&EmailConfigUpdateOptions{}, "email-config-update", "Update a email-config", func(s *mcclient.ClientSession, args *EmailConfigUpdateOptions) error { - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.USERNAME), "username") - params.Add(jsonutils.NewString(args.PASSWORD), "password") - params.Add(jsonutils.NewString(args.HOSTNAME), "hostname") - params.Add(jsonutils.NewString(args.SSLGLOBAL), "ssl_global") - params.Add(jsonutils.NewInt(args.HOSTPORT), "hostport") - - result, err := modules.EmailConfigs.Put(s, args.TYPE, params) - if err != nil { - return err - } - printObject(result) - return nil - }) - - /** - * 删除 - */ - type EmailConfigDeleteOptions struct { - TYPE string `help:"type of email "` - } - R(&EmailConfigDeleteOptions{}, "email-config-delete", "Delete a email config", func(s *mcclient.ClientSession, args *EmailConfigDeleteOptions) error { - result, e := modules.EmailConfigs.Delete(s, args.TYPE, nil) - if e != nil { - return e - } - printObject(result) - return nil - }) - -} diff --git a/cmd/climc/shell/notify/notification.go b/cmd/climc/shell/notify/notification.go deleted file mode 100644 index 7491ba0e32..0000000000 --- a/cmd/climc/shell/notify/notification.go +++ /dev/null @@ -1,161 +0,0 @@ -// 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 notify - -import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modules/notify" - "yunion.io/x/onecloud/pkg/mcclient/options" -) - -func init() { - - /** - * 新建一个通知发送任务 - */ - - type NotificationCreateOptions struct { - CONTACTTYPE string `help:"User's contacts type"` - TOPIC string `help:"Title or topic of the notification"` - PRIORITY string `help:"Priority of the notification" choices:"normal|important|fatal"` - MSG string `help:"The content of the notification"` - Remark string `help:"Remark or description of the notification"` - Group bool `help:"Send to group"` - } - type NotificationCreateSingleOptions struct { - UID string `help:"The user you wanna sent to (Keystone User ID)"` - NotificationCreateOptions - } - - R(&NotificationCreateSingleOptions{}, "notify", "Send a notification to someone", func(s *mcclient.ClientSession, - args *NotificationCreateSingleOptions) error { - - msg := notify.SNotifyMessage{} - if args.Group { - msg.Gid = []string{args.UID} - } else { - msg.Uid = []string{args.UID} - } - - msg.ContactType = notify.TNotifyChannel(args.CONTACTTYPE) - msg.Topic = args.TOPIC - msg.Priority = notify.TNotifyPriority(args.PRIORITY) - msg.Msg = args.MSG - msg.Remark = args.Remark - - err := notify.Notifications.Send(s, msg) - if err != nil { - return err - } - return nil - }) - - type NotificationCreateMultiOptions struct { - Uid []string `help:"The user you wanna sent to (Keystone User ID)"` - NotificationCreateOptions - } - - R(&NotificationCreateMultiOptions{}, "notify-batch", "Send a notification to someones", - func(s *mcclient.ClientSession, args *NotificationCreateMultiOptions) error { - - msg := notify.SNotifyMessage{} - if args.Group { - msg.Gid = args.Uid - } else { - msg.Uid = args.Uid - } - - msg.ContactType = notify.TNotifyChannel(args.CONTACTTYPE) - msg.Topic = args.TOPIC - msg.Priority = notify.TNotifyPriority(args.PRIORITY) - msg.Msg = args.MSG - msg.Remark = args.Remark - - err := notify.Notifications.Send(s, msg) - if err != nil { - return err - } - return nil - }) - - /** - * 发送全局通知 - */ - type NotificationBroadcastOptions struct { - // CONTACTTYPE string `help:"User's contacts type, cloud be email|mobile|dingtalk|/webconsole" choices:"email|mobile|dingtalk|webconsole"` - Topic string `required:"true" help:"Title or topic of the notification"` - Priority string `help:"Priority of the notification" choices:"normal|important|fatal" default:"normal"` - Msg string `help:"The content of the notification"` - Remark string `help:"Remark or description of the notification"` - // Group bool `help:"Send to group"` - } - - R(&NotificationBroadcastOptions{}, "notify-broadcast", "Send a notification to all online users", func(s *mcclient.ClientSession, args *NotificationBroadcastOptions) error { - msg := notify.SNotifyMessage{} - msg.Broadcast = true - msg.ContactType = notify.NotifyByWebConsole - msg.Topic = args.Topic - msg.Priority = notify.TNotifyPriority(args.Priority) - msg.Msg = args.Msg - msg.Remark = args.Remark - - err := notify.Notifications.Send(s, msg) - if err != nil { - return err - } - return nil - }) - /** - * 修改通知发送任务的状态 - */ - type NotificationUpdateCallbackOptions struct { - ID string `help:"ID of the notification send task"` - STATUS string `help:"Notification send status" choices:"sent_ok|send_fail"` - Remark string `help:"Remark or description of the operation or fail reason"` - } - R(&NotificationUpdateCallbackOptions{}, "notification-update-callback", "UpdateItem send status of the notification task", func(s *mcclient.ClientSession, args *NotificationUpdateCallbackOptions) error { - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.STATUS), "status") - if len(args.Remark) > 0 { - params.Add(jsonutils.NewString(args.Remark), "remark") - } - - notification, err := notify.Notifications.Put(s, args.ID, params) - if err != nil { - return err - } - printObject(notification) - return nil - }) - - /** - * 查询已发送的通知任务 - */ - type NotificationListOptions struct { - options.BaseListOptions - } - R(&NotificationListOptions{}, "notify-list", "List notification history", func(s *mcclient.ClientSession, args *NotificationListOptions) error { - result, err := notify.Notifications.List(s, nil) - if err != nil { - return err - } - - printList(result, notify.Notifications.GetColumns(s)) - return nil - }) - -} diff --git a/cmd/climc/shell/notify/notify.go b/cmd/climc/shell/notify/notify.go deleted file mode 100644 index 21b59dddc9..0000000000 --- a/cmd/climc/shell/notify/notify.go +++ /dev/null @@ -1,77 +0,0 @@ -// 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 notify - -import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modules" -) - -func init() { - - type ConfigCreate2Options struct { - CONTACTTYPE string `help:"contact type (email|sms_aliyun or others)"` - CONFIGS []string `help:"config (k, v)"` - } - R(&ConfigCreate2Options{}, "notify-config-update", "config update, example: notify_config-update email mail.smtp.hostname hostname mail.smtp.hostport 123.", func(s *mcclient.ClientSession, args *ConfigCreate2Options) error { - tmp := jsonutils.NewDict() - for i := 0; i+1 < len(args.CONFIGS); i += 2 { - tmp.Add(jsonutils.NewString(args.CONFIGS[i+1]), args.CONFIGS[i]) - } - body := jsonutils.NewDict() - body.Add(tmp, args.CONTACTTYPE) - ret, err := modules.Configs.Create(s, body) - if err != nil { - return err - } - printObject(ret) - return nil - }) - R(&ConfigCreate2Options{}, "notify-config-validate", "config validate", func(s *mcclient.ClientSession, - args *ConfigCreate2Options) error { - tmp := jsonutils.NewDict() - for i := 0; i+1 < len(args.CONFIGS); i += 2 { - tmp.Add(jsonutils.NewString(args.CONFIGS[i+1]), args.CONFIGS[i]) - } - ret, err := modules.Configs.PerformAction(s, args.CONTACTTYPE, "validate", tmp) - if err != nil { - return err - } - printObject(ret) - return nil - }) - - type ConfigGet2Options struct { - TYPE string `help:"contact type (email|sms_aliyun or others)"` - } - R(&ConfigGet2Options{}, "notify-config-show", "config show", func(s *mcclient.ClientSession, args *ConfigGet2Options) error { - result, err := modules.Configs.Get(s, args.TYPE, jsonutils.JSONNull) - if err != nil { - return err - } - printObject(result) - return nil - }) - R(&ConfigGet2Options{}, "notify-config-delete", "config delete", func(s *mcclient.ClientSession, args *ConfigGet2Options) error { - result, err := modules.Configs.Delete(s, args.TYPE, jsonutils.JSONNull) - if err != nil { - return err - } - printObject(result) - return nil - }) -} diff --git a/cmd/climc/shell/notify/notify_template.go b/cmd/climc/shell/notify/notify_template.go deleted file mode 100644 index 2a0581e761..0000000000 --- a/cmd/climc/shell/notify/notify_template.go +++ /dev/null @@ -1,90 +0,0 @@ -// 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 notify - -import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" -) - -func init() { - type NotifyTemplateUpdateOptions struct { - CONTACTTYPE string `help:"the contanct type, such as 'email', 'mobile'"` - TOPIC string `help:"the topic of temlate, such as 'VERIFY', 'ALARM'"` - TEMPLATETYPE string `help:"the type of template" choices:"content|title|remote"` - CONTENT string `help:"the content of template"` - } - R(&NotifyTemplateUpdateOptions{}, "notify-template-update", "Create, update contact for user", func(s *mcclient.ClientSession, - args *NotifyTemplateUpdateOptions) error { - arr := jsonutils.NewArray() - tmpObj := jsonutils.NewDict() - tmpObj.Add(jsonutils.NewString(args.TOPIC), "topic") - tmpObj.Add(jsonutils.NewString(args.TEMPLATETYPE), "template_type") - tmpObj.Add(jsonutils.NewString(args.CONTENT), "content") - arr.Add(tmpObj) - - params := jsonutils.NewDict() - params.Add(arr, "notifytemplates") - - contact, err := modules.NotifyTemplates.PerformAction(s, args.CONTACTTYPE, "update-template", params) - - if err != nil { - return err - } - - printObject(contact) - return nil - }) - - type NotifyTemplateDeleteOptions struct { - CONTACTTYPE string `help:"the contanct type, such as 'email', 'mobile'"` - Topic string `help:"the topic of temlate, such as 'VERIFY', 'ALARM'"` - } - - R(&NotifyTemplateDeleteOptions{}, "notify-template-delete", "delete notify template", - func(s *mcclient.ClientSession, args *NotifyTemplateDeleteOptions) error { - - tmpObj := jsonutils.NewDict() - tmpObj.Add(jsonutils.NewString(args.CONTACTTYPE), "contact_type") - tmpObj.Add(jsonutils.NewString(args.Topic), "topic") - _, err := modules.NotifyTemplates.DeleteContents(s, tmpObj) - if err != nil { - return err - } - return nil - }) - - type NotifyTemplateListOptions struct { - options.BaseListOptions - } - - R(&NotifyTemplateListOptions{}, "notify-template-list", "List all notify template", - func(s *mcclient.ClientSession, args *NotifyTemplateListOptions) error { - - params, err := args.BaseListOptions.Params() - if err != nil { - return err - } - result, err := modules.NotifyTemplates.List(s, params) - if err != nil { - return err - } - printList(result, modules.NotifyTemplates.GetColumns(s)) - return nil - }) -} diff --git a/cmd/climc/shell/notify/sms_configs.go b/cmd/climc/shell/notify/sms_configs.go deleted file mode 100644 index c6eb8d3f88..0000000000 --- a/cmd/climc/shell/notify/sms_configs.go +++ /dev/null @@ -1,124 +0,0 @@ -// 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 notify - -import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modules" -) - -func init() { - - /** - * 查询短信配置信息 - */ - type SmsConfigShowOptions struct { - TYPE string `help:"type "` - } - R(&SmsConfigShowOptions{}, "sms-config-show", "Show sms config details", - func(s *mcclient.ClientSession, args *SmsConfigShowOptions) error { - result, err := modules.SmsConfigs.Get(s, args.TYPE, nil) - if err != nil { - return err - } - printObject(result) - return nil - }) - - /** - * 增加短信配置信息 - */ - type SmsConfigCreateOptions struct { - TYPE string `help:"sms vendor"` - ACCESSKEYID string `help:"ACCESSKEYID for sms vendor"` - ACCESSKEYSECRET string `help:"ACCESSKEYSECRET for sms vendor"` - SIGNATURE string `help:"SIGNATURE for sms vendor"` - SmsTemplateOne string `help:"Sms TemplateOne"` - SmsTemplateTwo string `help:"Sms TemplateTwo"` - SmsTemplateThree string `help:"Sms TemplateThree"` - SmsCheckCode string `help:"Sms Check Code "` - } - - R(&SmsConfigCreateOptions{}, "sms-config-create", "Create a sms Config", - func(s *mcclient.ClientSession, args *SmsConfigCreateOptions) error { - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.TYPE), "type") - params.Add(jsonutils.NewString(args.ACCESSKEYID), "access_key_id") - params.Add(jsonutils.NewString(args.ACCESSKEYSECRET), "access_key_secret") - params.Add(jsonutils.NewString(args.SIGNATURE), "signature") - params.Add(jsonutils.NewString(args.SmsTemplateOne), "sms_template_one") - params.Add(jsonutils.NewString(args.SmsTemplateTwo), "sms_template_two") - params.Add(jsonutils.NewString(args.SmsTemplateThree), "sms_template_three") - params.Add(jsonutils.NewString(args.SmsCheckCode), "sms_check_code") - - result, err := modules.SmsConfigs.Create(s, params) - if err != nil { - return err - } - printObject(result) - return nil - }) - - /** - * 修改 - */ - type SmsConfigUpdateOptions struct { - TYPE string `help:"sms vendor"` - ACCESSKEYID string `help:"ACCESSKEYID for sms vendor"` - ACCESSKEYSECRET string `help:"ACCESSKEYSECRET for sms vendor"` - SIGNATURE string `help:"SIGNATURE for sms vendor"` - SmsTemplateOne string `help:"Sms TemplateOne"` - SmsTemplateTwo string `help:"Sms TemplateTwo"` - SmsTemplateThree string `help:"Sms TemplateThree"` - SmsCheckCode string `help:"Sms Check Code "` - } - R(&SmsConfigUpdateOptions{}, "sms-config-update", "Update a sms-config", func(s *mcclient.ClientSession, args *SmsConfigUpdateOptions) error { - params := jsonutils.NewDict() - - params.Add(jsonutils.NewString(args.TYPE), "type") - params.Add(jsonutils.NewString(args.ACCESSKEYID), "access_key_id") - params.Add(jsonutils.NewString(args.ACCESSKEYSECRET), "access_key_secret") - params.Add(jsonutils.NewString(args.SIGNATURE), "signature") - params.Add(jsonutils.NewString(args.SmsTemplateOne), "sms_template_one") - params.Add(jsonutils.NewString(args.SmsTemplateTwo), "sms_template_two") - params.Add(jsonutils.NewString(args.SmsTemplateThree), "sms_template_three") - params.Add(jsonutils.NewString(args.SmsCheckCode), "sms_check_code") - - result, err := modules.SmsConfigs.Put(s, args.TYPE, params) - if err != nil { - return err - } - printObject(result) - return nil - }) - - /** - * 删除 - */ - type SmsConfigDeleteOptions struct { - TYPE string `help:"sms vendor"` - } - R(&SmsConfigDeleteOptions{}, "sms-config-delete", "Delete a sms config", func(s *mcclient.ClientSession, args *SmsConfigDeleteOptions) error { - result, e := modules.SmsConfigs.Delete(s, args.TYPE, nil) - if e != nil { - return e - } - printObject(result) - return nil - }) - -} diff --git a/cmd/climc/shell/notify/verification.go b/cmd/climc/shell/notify/verification.go deleted file mode 100644 index f65fcf44a8..0000000000 --- a/cmd/climc/shell/notify/verification.go +++ /dev/null @@ -1,47 +0,0 @@ -// 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 notify - -import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modules" -) - -func init() { - - /** - * 通信地址验证 - */ - type ContactsVerifyOptions struct { - ID string `help:"Verification process ID"` - TOKEN string `help:"Temporary token issued to the user when the validation is triggered"` - } - R(&ContactsVerifyOptions{}, "contact-verify", "Trigger contact verify", func(s *mcclient.ClientSession, args *ContactsVerifyOptions) error { - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.TOKEN), "token") - - result, err := modules.Verifications.Get(s, args.ID, params) - - if err != nil { - return err - } - - printObject(result) - return nil - }) - -} diff --git a/cmd/climc/shell/notify/common.go b/cmd/climc/shell/notifyv2/common.go similarity index 98% rename from cmd/climc/shell/notify/common.go rename to cmd/climc/shell/notifyv2/common.go index 175530d019..235bc49899 100644 --- a/cmd/climc/shell/notify/common.go +++ b/cmd/climc/shell/notifyv2/common.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package notify +package notifyv2 import ( "yunion.io/x/onecloud/cmd/climc/shell" diff --git a/cmd/climc/shell/notifyv2/config.go b/cmd/climc/shell/notifyv2/config.go new file mode 100644 index 0000000000..9a2e494a62 --- /dev/null +++ b/cmd/climc/shell/notifyv2/config.go @@ -0,0 +1,123 @@ +// 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 notifyv2 + +import ( + "strings" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type ConfigListOptions struct { + options.BaseListOptions + } + R(&ConfigListOptions{}, "notify-config-list", "List notify config", func(s *mcclient.ClientSession, args *ConfigListOptions) error { + params, err := options.ListStructToParams(args) + if err != nil { + return err + } + result, err := modules.NotifyConfig.List(s, params) + if err != nil { + return err + } + printList(result, modules.NotifyConfig.GetColumns(s)) + return nil + }) + type ConfigCreateOptions struct { + TYPE string `help:"Type contact config"` + Configs []string `help:"Config content, format: 'key:value'"` + } + R(&ConfigCreateOptions{}, "notify-config-create", "Create notify config", func(s *mcclient.ClientSession, args *ConfigCreateOptions) error { + configs := jsonutils.NewDict() + for _, kv := range args.Configs { + index := strings.IndexByte(kv, ':') + configs.Set(kv[:index], jsonutils.NewString(kv[index+1:])) + } + params := jsonutils.NewDict() + params.Set("type", jsonutils.NewString(args.TYPE)) + params.Set("content", configs) + ret, err := modules.NotifyConfig.Create(s, params) + if err != nil { + return err + } + printObject(ret) + return nil + }) + R(&ConfigCreateOptions{}, "notify-config-update", "Update notify config", func(s *mcclient.ClientSession, args *ConfigCreateOptions) error { + configs := jsonutils.NewDict() + for _, kv := range args.Configs { + index := strings.IndexByte(kv, ':') + configs.Set(kv[:index], jsonutils.NewString(kv[index+1:])) + } + params := jsonutils.NewDict() + params.Set("content", configs) + + id, err := configIdFromType(s, args.TYPE) + if err != nil { + return err + } + ret, err := modules.NotifyConfig.Update(s, id, params) + if err != nil { + return err + } + printObject(ret) + return nil + }) + type ConfigOptions struct { + TYPE string `help:"Type contact config"` + } + R(&ConfigOptions{}, "notify-config-delete", "Delete notify config", func(s *mcclient.ClientSession, args *ConfigOptions) error { + id, err := configIdFromType(s, args.TYPE) + if err != nil { + return err + } + ret, err := modules.NotifyConfig.Delete(s, id, nil) + if err != nil { + return err + } + printObject(ret) + return nil + }) + R(&ConfigOptions{}, "notify-config-show", "Show notify config", func(s *mcclient.ClientSession, args *ConfigOptions) error { + listParams := jsonutils.NewDict() + listParams.Set("type", jsonutils.NewString(args.TYPE)) + list, err := modules.NotifyConfig.List(s, listParams) + if err != nil { + return err + } + data := list.Data[0] + printObject(data) + return nil + }) +} + +func configIdFromType(s *mcclient.ClientSession, t string) (string, error) { + listParams := jsonutils.NewDict() + listParams.Set("type", jsonutils.NewString(t)) + list, err := modules.NotifyConfig.List(s, listParams) + if err != nil { + return "", err + } + id, err := list.Data[0].GetString("id") + if err != nil { + return "", err + } + return id, nil +} diff --git a/cmd/climc/shell/notifyv2/notification.go b/cmd/climc/shell/notifyv2/notification.go new file mode 100644 index 0000000000..e9d0940a37 --- /dev/null +++ b/cmd/climc/shell/notifyv2/notification.go @@ -0,0 +1,78 @@ +// 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 notifyv2 + +import ( + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type NotificationCreateInput struct { + Receivers []string `help:"ID or Name of Receiver"` + ContactType string `help:"Contact type of receiver"` + TOPIC string `help:"Topic"` + Priority string `help:"Priority"` + MESSAGE string `help:"Message"` + } + R(&NotificationCreateInput{}, "notify-send", "Send a notify message", func(s *mcclient.ClientSession, args *NotificationCreateInput) error { + input := api.NotificationCreateInput{ + Receivers: args.Receivers, + ContactType: args.ContactType, + Topic: args.TOPIC, + Priority: args.Priority, + Message: args.MESSAGE, + } + ret, err := modules.Notification.Create(s, jsonutils.Marshal(input)) + if err != nil { + return err + } + printObject(ret) + return nil + }) + type NotificationInput struct { + ID string `help:"Id of notification"` + } + R(&NotificationInput{}, "notify-show", "Show a notify message", func(s *mcclient.ClientSession, args *NotificationInput) error { + ret, err := modules.Notification.Get(s, args.ID, nil) + if err != nil { + return nil + } + printObject(ret) + return nil + }) + type NotificationListInput struct { + options.BaseListOptions + + ContactType string `help:"contact_type"` + ReceiverId string `help:"receiver_id"` + } + R(&NotificationListInput{}, "notify-list", "List notify message", func(s *mcclient.ClientSession, args *NotificationListInput) error { + params, err := options.ListStructToParams(args) + if err != nil { + return err + } + ret, err := modules.Notification.List(s, params) + if err != nil { + return err + } + printList(ret, modules.Notification.GetColumns(s)) + return nil + }) +} diff --git a/cmd/climc/shell/notifyv2/receiver.go b/cmd/climc/shell/notifyv2/receiver.go new file mode 100644 index 0000000000..c78132d4bd --- /dev/null +++ b/cmd/climc/shell/notifyv2/receiver.go @@ -0,0 +1,165 @@ +// 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 notifyv2 + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type ReceiverListOptions struct { + options.BaseListOptions + UId string `help:"user id in keystone"` + UName string `help:"user name in keystone"` + EnabledContactType string `help:"enabled contact type"` + VerifiedContactType string `help:"verified contact type"` + } + R(&ReceiverListOptions{}, "notify-receiver-list", "List notify receiver", func(s *mcclient.ClientSession, args *ReceiverListOptions) error { + params, err := options.ListStructToParams(args) + if err != nil { + return err + } + result, err := modules.NotifyReceiver.List(s, params) + if err != nil { + return err + } + printList(result, modules.NotifyReceiver.GetColumns(s)) + return nil + }) + type ReceiverCreateOptions struct { + UID string `help:"user id in keystone"` + Email string `help:"email of receiver"` + Mobile string `help:"mobile of receiver"` + EnabledContactTypes []string `help:"enabled contact type"` + } + R(&ReceiverCreateOptions{}, "notify-receiver-create", "Create notify receiver", func(s *mcclient.ClientSession, args *ReceiverCreateOptions) error { + params := jsonutils.Marshal(args).(*jsonutils.JSONDict) + receiver, err := modules.NotifyReceiver.Create(s, params) + if err != nil { + return err + } + printObject(receiver) + return nil + }) + type ReceiverUpdateInput struct { + ID string `help:"Id or Name of receiver"` + Email string `help:"email of receiver"` + Mobile string `help:"mobile of receiver"` + EnabledContactType []string `help:"enabled contact type"` + } + R(&ReceiverUpdateInput{}, "notify-receiver-update", "Update notify receiver", func(s *mcclient.ClientSession, args *ReceiverUpdateInput) error { + params := jsonutils.NewDict() + if len(args.Email) > 0 { + params.Set("email", jsonutils.NewString(args.Email)) + } + if len(args.Mobile) > 0 { + params.Set("mobile", jsonutils.NewString(args.Mobile)) + } + if len(args.EnabledContactType) > 0 { + params.Set("enabled_contact_types", jsonutils.NewStringArray(args.EnabledContactType)) + } + ret, err := modules.NotifyReceiver.Update(s, args.ID, params) + if err != nil { + return err + } + printObject(ret) + return nil + }) + type ReceiverOptions struct { + ID string `help:"Id or Name of receiver"` + } + R(&ReceiverOptions{}, "notify-receiver-show", "Show notify receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error { + ret, err := modules.NotifyReceiver.Get(s, args.ID, nil) + if err != nil { + return err + } + printObject(ret) + return nil + }) + R(&ReceiverOptions{}, "notify-receiver-delete", "Delete notify receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error { + receiver, err := modules.NotifyReceiver.Delete(s, args.ID, jsonutils.NewDict()) + if err != nil { + return err + } + printObject(receiver) + return nil + }) + R(&ReceiverOptions{}, "notify-receiver-enable", "Enable notify receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error { + ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "enable", jsonutils.NewDict()) + if err != nil { + return err + } + printObject(ret) + return nil + }) + R(&ReceiverOptions{}, "notify-receiver-disable", "Disable notify receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error { + ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "disable", jsonutils.NewDict()) + if err != nil { + return err + } + printObject(ret) + return nil + }) + type ReceiverTriggerVerifyInput struct { + ID string `help:"Id or Name of receiver"` + ContactType string `help:"Contact type to trigger verify" choices:"email|mobile"` + } + R(&ReceiverTriggerVerifyInput{}, "notify-receiver-trigger-verify", "Trigger verification for receiver about some contact", func(s *mcclient.ClientSession, args *ReceiverTriggerVerifyInput) error { + params := jsonutils.NewDict() + params.Set("contact_type", jsonutils.NewString(args.ContactType)) + ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "trigger-verify", params) + if err != nil { + return err + } + printObject(ret) + return nil + }) + type ReceiverVerifyInput struct { + ID string `help:"Id or Name of receiver"` + ContactType string `help:"Contact type to trigger verify" choices:"email|mobile"` + Token string `help:"Token from verify message sent to you"` + } + R(&ReceiverVerifyInput{}, "notify-receiver-verify", "Verify receiver about some contact type", func(s *mcclient.ClientSession, args *ReceiverVerifyInput) error { + params := jsonutils.NewDict() + params.Set("contact_type", jsonutils.NewString(args.ContactType)) + params.Set("token", jsonutils.NewString(args.Token)) + ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "verify", params) + if err != nil { + return err + } + printObject(ret) + return nil + }) + R(&ReceiverOptions{}, "notify-receiver-enable", "Enable receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error { + ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "enable", nil) + if err != nil { + return err + } + printObject(ret) + return nil + }) + R(&ReceiverOptions{}, "notify-receiver-disable", "Disable receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error { + ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "disable", nil) + if err != nil { + return err + } + printObject(ret) + return nil + }) +} diff --git a/cmd/climc/shell/notifyv2/template.go b/cmd/climc/shell/notifyv2/template.go new file mode 100644 index 0000000000..a74d13d64e --- /dev/null +++ b/cmd/climc/shell/notifyv2/template.go @@ -0,0 +1,106 @@ +// 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 notifyv2 + +import ( + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type TemplateCreateInput struct { + NAME string `help:"Name"` + ContactType string `help:"Contact type, specifically, setting it to all means all contact type"` + TemplateType string `help:"Template type"` + Topic string `help:"Template topic"` + Content string `help:"Template content"` + Example string `help:"Example for using this template"` + } + R(&TemplateCreateInput{}, "notify-template-create", "Create notify template", func(s *mcclient.ClientSession, args *TemplateCreateInput) error { + input := api.TemplateCreateInput{ + ContactType: args.ContactType, + TemplateType: args.TemplateType, + Topic: args.Topic, + Content: args.Content, + Example: args.Example, + } + input.Name = args.NAME + ret, err := modules.NotifyTemplate.Create(s, jsonutils.Marshal(input)) + if err != nil { + return err + } + printObject(ret) + return nil + }) + type TemplateListInput struct { + options.BaseListOptions + + ContactType string `help:"Contact type"` + TemplateType string `help:"Template type"` + Topic string `help:"Topic"` + } + R(&TemplateListInput{}, "notify-template-list", "List notify template", func(s *mcclient.ClientSession, args *TemplateListInput) error { + params, err := options.ListStructToParams(args) + if err != nil { + return err + } + list, err := modules.NotifyTemplate.List(s, params) + if err != nil { + return err + } + printList(list, modules.NotifyTemplate.GetColumns(s)) + return nil + }) + type TemplateInput struct { + ID string `help:"id or name of template"` + } + R(&TemplateInput{}, "notify-template-get", "Get notify template", func(s *mcclient.ClientSession, args *TemplateInput) error { + ret, err := modules.NotifyTemplate.Get(s, args.ID, nil) + if err != nil { + return err + } + printObject(ret) + return nil + }) + type TemplateUpdateInput struct { + ID string `help:"id or name of template"` + Content string `help:"Template content"` + Example string `help:"Example for using this template"` + } + R(&TemplateUpdateInput{}, "notify-template-update", "Update notify template", func(s *mcclient.ClientSession, args *TemplateUpdateInput) error { + input := api.TemplateUpdateInput{ + Content: args.Content, + Example: args.Example, + } + ret, err := modules.NotifyTemplate.Update(s, args.ID, jsonutils.Marshal(input)) + if err != nil { + return err + } + printObject(ret) + return nil + }) + R(&TemplateInput{}, "notify-template-delete", "Delete notify template", func(s *mcclient.ClientSession, args *TemplateInput) error { + ret, err := modules.NotifyTemplate.Delete(s, args.ID, nil) + if err != nil { + return err + } + printObject(ret) + return nil + }) +} diff --git a/cmd/notify/main.go b/cmd/notify/main.go index 0a733e2679..6a499c2283 100644 --- a/cmd/notify/main.go +++ b/cmd/notify/main.go @@ -15,9 +15,9 @@ package main import ( - "yunion.io/x/onecloud/pkg/notify" + "yunion.io/x/onecloud/pkg/notify/service" ) func main() { - notify.StartService() + service.StartService() } diff --git a/pkg/apis/notify/config.go b/pkg/apis/notify/config.go new file mode 100644 index 0000000000..84bd2b76b4 --- /dev/null +++ b/pkg/apis/notify/config.go @@ -0,0 +1,63 @@ +// 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 notify + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/apis" +) + +type ConfigCreateInput struct { + apis.StandaloneResourceCreateInput + + // description: config type + // required: true + // example: feishu + Type string `json:"type"` + + // description: config content + // required: true + // example: {"app_id": "123456", "app_secret": "feishu_nihao"} + Content jsonutils.JSONObject `json:"content"` +} + +type ConfigDetails struct { + apis.StandaloneResourceDetails + + SConfig +} + +type ConfigListInput struct { + apis.StandaloneResourceListInput + Type string `json:"type"` +} + +type ConfigValidateInput struct { + // description: config type + // required: true + // example: feishu + Type string `json:"type"` + + // description: config content + // required: true + // example: {"app_id": "123456", "app_secret": "feishu_nihao"} + Content jsonutils.JSONObject `json:"content"` +} + +type ConfigValidateOutput struct { + IsValid bool `json:"is_valid"` + Message string `json:"message"` +} diff --git a/pkg/apis/notify/const.go b/pkg/apis/notify/const.go new file mode 100644 index 0000000000..3a2d0ac6e0 --- /dev/null +++ b/pkg/apis/notify/const.go @@ -0,0 +1,63 @@ +// 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 notify + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + SERVICE_TYPE = apis.SERVICE_TYPE_NOTIFY + SERVICE_VERSION = "" + + EMAIL = "email" + MOBILE = "mobile" + DINGTALK = "dingtalk" + FEISHU = "feishu" + WEBCONSOLE = "webconsole" + WORKWX = "workwx" + FEISHU_ROBOT = "feishu-robot" + DINGTALK_ROBOT = "dingtalk-robot" + WORKWX_ROBOT = "workwx-robot" + + ROBOT = "robot" + + RECEIVER_NOTIFICATION_RECEIVED = "received" // Received a task about sending a notification + RECEIVER_NOTIFICATION_SENT = "sending" // Nofity module has sent notification, but result unkown + RECEIVER_NOTIFICATION_OK = "sent_ok" // Notification was sent successfully + RECEIVER_NOTIFICATION_FAIL = "sent_fail" // That sent a notification is failed + + VERIFICATION_SENT = "sent" // Verification was sent + VERIFICATION_SENT_FAIL = "sent_fail" // Verification was sent failed + VERIFICATION_VERIFIED = "verified" // Verification was verified + VERIFICATION_TOKEN_EXPIRED = "Verification code expired" + VERIFICATION_TOKEN_INVALID = "Incorrect verification code" + + RECEIVER_STATUS_READY = "ready" + RECEIVER_STATUS_PULLING = "pulling" + RECEIVER_STATUS_PULL_FAILED = "pull_failed" + + NOTIFICATION_PRIORITY_IMPORTANT = "important" + NOTIFICATION_PRIORITY_CRITICAL = "fatal" + NOTIFICATION_PRIORITY_NORMAL = "normal" + + NOTIFICATION_STATUS_RECEIVED = "received" + NOTIFICATION_STATUS_SENDING = "sending" + NOTIFICATION_STATUS_FAILED = "failed" + NOTIFICATION_STATUS_OK = "ok" + NOTIFICATION_STATUS_PART_OK = "part_ok" + + TEMPLATE_TYPE_TITLE = "title" + TEMPLATE_TYPE_CONTENT = "content" + TEMPLATE_TYPE_REMOTE = "remote" +) diff --git a/pkg/apis/notify/contact.go b/pkg/apis/notify/contact.go deleted file mode 100644 index 05f618bc1a..0000000000 --- a/pkg/apis/notify/contact.go +++ /dev/null @@ -1,25 +0,0 @@ -// 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 notify - -import "yunion.io/x/onecloud/pkg/apis" - -type ContactDetails struct { - apis.ResourceBaseDetails - - UID string `json:"uid"` - Name string `json:"name"` - Details string `json:"details"` -} diff --git a/pkg/apis/notify/notification.go b/pkg/apis/notify/notification.go index 96fc3ddad7..c3dead2c70 100644 --- a/pkg/apis/notify/notification.go +++ b/pkg/apis/notify/notification.go @@ -15,18 +15,62 @@ package notify import ( - "yunion.io/x/jsonutils" + "time" "yunion.io/x/onecloud/pkg/apis" ) -type NotificationDetails struct { - apis.ResourceBaseDetails +type NotificationCreateInput struct { + apis.StatusStandaloneResourceCreateInput - UserList jsonutils.JSONObject `json:"user_list"` + // description: ids or names of receiver + // required: false + // example: {"adfb720ccdd34c638346ea4fa7a713a8", "zhangsan"} + Receivers []string `json:"receivers"` + // description: direct contact, admin privileges required + // required: false + Contacts []string `json:"contacts"` + // description: contact type + // required: ture + // example: email + ContactType string `json:"contact_type"` + // description: notification topic + // required: true + // example: IMAGE_ACTIVE + Topic string `json:"topic"` + // description: notification priority + // required: false + // enum: fatal,important,nomal + // example: normal + Priority string `json:"priority"` + // description: message content or jsonobject + // required: ture + Message string `json:"message"` +} + +type ReceiveDetail struct { + ReceiverId string `json:"receiver_id"` + ReceiverName string `json:"receiver_name"` + Contact string `json:"contact"` + SendAt time.Time `json:"sendAt"` + SendBy string `json:"send_by"` + Status string `json:"status"` + FailedReason string `json:"failed_reason"` +} + +type NotificationDetails struct { + apis.StatusStandaloneResourceDetails + + SNotification + + Title string `json:"title"` + Content string `json:"content"` + ReceiveDetails []ReceiveDetail `json:"receive_details"` } type NotificationListInput struct { - Scope string `json:"scope"` - ContactType string `json:"contact_type"` + apis.StatusStandaloneResourceListInput + + ContactType string + ReceiverId string } diff --git a/pkg/apis/notify/notify.go b/pkg/apis/notify/notify.go deleted file mode 100644 index 58e3ef7838..0000000000 --- a/pkg/apis/notify/notify.go +++ /dev/null @@ -1,22 +0,0 @@ -// 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 notify - -import "yunion.io/x/onecloud/pkg/apis" - -const ( - SERVICE_TYPE = apis.SERVICE_TYPE_NOTIFY - SERVICE_VERSION = "" -) diff --git a/pkg/apis/notify/receiver.go b/pkg/apis/notify/receiver.go new file mode 100644 index 0000000000..500af9f005 --- /dev/null +++ b/pkg/apis/notify/receiver.go @@ -0,0 +1,110 @@ +// 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 notify + +import ( + "yunion.io/x/onecloud/pkg/apis" +) + +type ReceiverCreateInput struct { + apis.StatusStandaloneResourceCreateInput + apis.DomainizedResourceCreateInput + apis.EnabledBaseResourceCreateInput + + // description: user id in keystone + // example: adfb720ccdd34c638346ea4fa7a713a8 + UID string `json:"uid"` + + // description: user name in keystone + // example: hello + UName string `json:"uname"` + + // description: user email + // example: example@gmail.com + Email string `json:"email"` + + // description: user mobile + // example: 17812345678 + Mobile string `json:"mobile"` + + // description: enabled contact types for user + // example: {"email", "mobile", "feishu", "dingtalk", "workwx"} + EnabledContactTypes []string `json:"enabled_contact_types"` +} + +type ReceiverDetails struct { + apis.StatusStandaloneResourceDetails + apis.DomainizedResourceInfo + + SReceiver + + // description: enabled contact types for user + // example: eamil, mobile, feishu, dingtalk, workwx + EnabledContactTypes []string `json:"enabled_contact_types"` + + // description: verified contact types for user + // example: email, mobile, feishu, dingtalk, workwx + VerifiedContactTypes []string `json:"verified_contact_types"` +} + +type ReceiverListInput struct { + apis.StatusStandaloneResourceListInput + apis.DomainizedResourceListInput + apis.EnabledResourceBaseListInput + + UID string `json:"uid"` + + UName string `json:"uname"` + + EnabledContactType string `json:"enabled_contact_type"` + + VerifiedContactType string `json:"verified_contact_type"` +} + +type ReceiverUpdateInput struct { + apis.StatusStandaloneResourceBaseUpdateInput + + // description: user email + // example: example@gmail.com + Email string `json:"email"` + + // description: user mobile + // example: 17812345678 + Mobile string `json:"mobile"` + + // description: enabled contacts for user + // example: {"email", "mobile", "feishu", "dingtalk", "workwx"} + EnabledContactTypes []string `json:"enabled_contact_types"` +} + +type ReceiverTriggerVerifyInput struct { + // description: contact type + // required: true + // example: email + // enum: email,mobile + ContactType string `json:"contact_type"` +} + +type ReceiverVerifyInput struct { + // description: Contact type + // required: true + // example: email + // enum: email,mobile + ContactType string `json:"contact_type"` + // description: token user input + // required: true + // example: 123456 + Token string `json:"token"` +} diff --git a/pkg/apis/notify/template.go b/pkg/apis/notify/template.go new file mode 100644 index 0000000000..751d0f70ae --- /dev/null +++ b/pkg/apis/notify/template.go @@ -0,0 +1,82 @@ +// 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 notify + +import "yunion.io/x/onecloud/pkg/apis" + +type TemplateCreateInput struct { + apis.StandaloneResourceCreateInput + + // description: Contact type, specifically, setting it to all means all contact type + // require: true + // example: email + ContactType string `json:"contact_type"` + // description: Template type + // enum: title,content,remote + // example: title + TemplateType string `json:"template_type"` + + // description: Template topic + // required: true + // example: IMAGE_ACTIVE + Topic string `json:"topic"` + + // description: Template content + // required: true + // example: 镜像 {{.name}} 上传完成 + Content string `json:"content"` + // description: Example for using this template + // required: true + // example: {"name": "centos7.6"} + Example string `json:"example"` +} + +type TemplateListInput struct { + apis.StandaloneResourceListInput + + // description: Contact type, specifically, setting it to all means all contact type + // require: true + // example: email + ContactType string `json:"contact_type"` + + // description: Template type + // enum: title,content,remote + // example: title + TemplateType string `json:"template_type"` + + // description: template topic + // required: true + // example: IMAGE_ACTIVE + Topic string `json:"topic"` +} + +type TemplateUpdateInput struct { + apis.StandaloneResourceCreateInput + + // description: template content + // required: true + // example: 镜像 {{.name}} 上传完成 + Content string `json:"content"` + // description: all example for using this template + // required: true + // example: {"name": "centos7.6"} + Example string `json:"example"` +} + +type TemplateDetails struct { + apis.StandaloneResourceDetails + + STemplate +} diff --git a/pkg/apis/notify/zz_generated.model.go b/pkg/apis/notify/zz_generated.model.go new file mode 100644 index 0000000000..fc90209557 --- /dev/null +++ b/pkg/apis/notify/zz_generated.model.go @@ -0,0 +1,101 @@ +// 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. + +// Code generated by model-api-gen. DO NOT EDIT. + +package notify + +import ( + time "time" + + "yunion.io/x/onecloud/pkg/apis" +) + +// SConfig is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SConfig. +type SConfig struct { + apis.SStandaloneResourceBase + Type string `json:"type"` + Content interface{} `json:"content"` +} + +// SNotification is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SNotification. +type SNotification struct { + apis.SStatusStandaloneResourceBase + ContactType string `json:"contact_type"` + // swagger:ignore + Topic string `json:"topic"` + Priority string `json:"priority"` + // swagger:ignore + Message string `json:"message"` + ReceivedAt time.Time `json:"received_at"` + SendTimes int `json:"send_times"` +} + +// SReceiver is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SReceiver. +type SReceiver struct { + apis.SStatusStandaloneResourceBase + apis.SDomainizedResourceBase + apis.SEnabledResourceBase + Email string `json:"email"` + Mobile string `json:"mobile"` + // swagger:ignore + EnabledEmail *bool `json:"enabled_email,omitempty"` + // swagger:ignore + VerifiedEmail *bool `json:"verified_email,omitempty"` + // swagger:ignore + EnabledMobile *bool `json:"enabled_mobile,omitempty"` + // swagger:ignore + VerifiedMobile *bool `json:"verified_mobile,omitempty"` +} + +// SReceiverNotification is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SReceiverNotification. +type SReceiverNotification struct { + apis.SJointResourceBase + RecevierID string `json:"recevier_id"` + NotificationID string `json:"notification_id"` + // ignore if RecevierID is not empty + Contact string `json:"contact"` + SendBy string `json:"send_by"` + Status string `json:"status"` + FailedReason string `json:"failed_reason"` +} + +// SSubContact is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubContact. +type SSubContact struct { + apis.SResourceBase + // id of receiver user + RecevierID string `json:"recevier_id"` + Type string `json:"type"` + Contact string `json:"contact"` + ParentContactType string `json:"parent_contact_type"` + Enabled *bool `json:"enabled,omitempty"` + Verified *bool `json:"verified,omitempty"` +} + +// STemplate is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.STemplate. +type STemplate struct { + apis.SStandaloneResourceBase + ContactType string `json:"contact_type"` + Topic string `json:"topic"` + // title | content | remote + TemplateType string `json:"template_type"` + Content string `json:"content"` + Example string `json:"example"` +} + +// SVerification is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SVerification. +type SVerification struct { + apis.SResourceBase + ReceiverId string `json:"receiver_id"` + ContactType string `json:"contact_type"` + Token string `json:"token"` +} diff --git a/pkg/mcclient/modules/managers.go b/pkg/mcclient/modules/managers.go index 32f0a889d0..5cfa384336 100644 --- a/pkg/mcclient/modules/managers.go +++ b/pkg/mcclient/modules/managers.go @@ -74,6 +74,14 @@ func NewNotifyManager(keyword, keywordPlural string, columns, adminColumns []str Keyword: keyword, KeywordPlural: keywordPlural} } +func NewNotifyv2Manager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { + return modulebase.ResourceManager{ + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_NOTIFY, "", "v2", columns, adminColumns), + Keyword: keyword, + KeywordPlural: keywordPlural, + } +} + func NewJointComputeManager(keyword, keywordPlural string, columns, adminColumns []string, master, slave modulebase.Manager) modulebase.JointResourceManager { return modulebase.JointResourceManager{ ResourceManager: NewComputeManager(keyword, keywordPlural, columns, adminColumns), diff --git a/pkg/mcclient/modules/mod_notify.go b/pkg/mcclient/modules/mod_notify.go index 6552269f60..d348de9f6a 100644 --- a/pkg/mcclient/modules/mod_notify.go +++ b/pkg/mcclient/modules/mod_notify.go @@ -21,13 +21,43 @@ type ConfigsManager struct { } var ( - Configs ConfigsManager + NotifyReceiver modulebase.ResourceManager + NotifyConfig modulebase.ResourceManager + Notification modulebase.ResourceManager + NotifyTemplate modulebase.ResourceManager + Configs ConfigsManager ) func init() { - Configs = ConfigsManager{NewNotifyManager("config", "configs", + NotifyReceiver = NewNotifyv2Manager( + "receiver", + "receivers", + []string{"ID", "Name", "Email", "Mobile", "Enabled_Contact_Types", "Verified_Contact_Types"}, []string{}, - []string{})} + ) + register(&NotifyReceiver) - register(&Configs) + NotifyConfig = NewNotifyv2Manager( + "notifyconfig", + "notifyconfigs", + []string{"Type", "Content"}, + []string{}, + ) + register(&NotifyConfig) + + Notification = NewNotifyv2Manager( + "notification", + "notifications", + []string{"Title", "Content", "ContactType", "Priority", "Receiver_Details"}, + []string{}, + ) + register(&Notification) + + NotifyTemplate = NewNotifyv2Manager( + "notifytemplate", + "notifytemplates", + []string{"ID", "Name", "Contact_Type", "Topic", "Template_Type", "Content", "Example"}, + []string{}, + ) + register(&NotifyTemplate) } diff --git a/pkg/mcclient/modules/mod_notify_template.go b/pkg/mcclient/modules/mod_notify_template.go deleted file mode 100644 index 42ad402635..0000000000 --- a/pkg/mcclient/modules/mod_notify_template.go +++ /dev/null @@ -1,43 +0,0 @@ -// 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 modules - -import ( - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modulebase" -) - -type NotifyTemplateManager struct { - modulebase.ResourceManager -} - -func (nm *NotifyTemplateManager) DeleteContents(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - path := "/notifytemplates/delete-template" - return modulebase.Post(nm.ResourceManager, s, path, params, "") -} - -var ( - NotifyTemplates NotifyTemplateManager -) - -func init() { - NotifyTemplates = NotifyTemplateManager{NewNotifyManager("notifytemplate", "notifytemplates", - []string{"Contact_Type", "Topic", "Template_Type"}, - []string{})} - - register(&NotifyTemplates) -} diff --git a/pkg/mcclient/modules/notify/mod_notification.go b/pkg/mcclient/modules/notify/mod_notification.go index ecf98e79be..1ae0d2b37d 100644 --- a/pkg/mcclient/modules/notify/mod_notification.go +++ b/pkg/mcclient/modules/notify/mod_notification.go @@ -15,9 +15,9 @@ package notify import ( - "fmt" - "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/sets" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modulebase" @@ -39,26 +39,54 @@ type SNotifyMessage struct { Broadcast bool `json:"broadcast,omitempty"` } +type SNotifyV2Message struct { + ReceiverIds []string `json:"receiver_ids"` + ContactType string `json:"contact_type"` + Topic string `json:"topic"` + Priority string `json:"priority"` + Message string `json:"message"` +} + type NotificationManager struct { modulebase.ResourceManager } func (manager *NotificationManager) Send(s *mcclient.ClientSession, msg SNotifyMessage) error { - params := jsonutils.Marshal(&msg) - body := jsonutils.NewDict() - body.Add(params, manager.Keyword) + receiverIds := make([]string, 0, len(msg.Uid)) + if len(msg.Gid) > 0 { + // fetch uid + uidSet := sets.NewString() + for _, gid := range msg.Gid { + users, err := modules.Groups.GetUsers(s, gid, nil) + if err != nil { + return errors.Wrapf(err, "Groups.GetUsers for group %q", gid) + } + for i := range users.Data { + id, _ := users.Data[i].GetString("id") + uidSet.Insert(id) + } + } + for _, uid := range uidSet.UnsortedList() { + receiverIds = append(receiverIds, uid) + } + } + receiverIds = append(receiverIds, msg.Uid...) - path := fmt.Sprintf("/%s?uname=true", manager.ContextPath(nil)) - _, err := modulebase.Post(manager.ResourceManager, s, path, body, manager.KeywordPlural) + v2msg := SNotifyV2Message{ + ReceiverIds: receiverIds, + ContactType: string(msg.ContactType), + Topic: msg.Topic, + Priority: string(msg.Priority), + Message: msg.Msg, + } + params := jsonutils.Marshal(&v2msg) + + _, err := manager.Create(s, params) return err } func init() { Notifications = NotificationManager{ - modules.NewNotifyManager("notification", "notifications", - []string{"id", "uid", "contact_type", "topic", "priority", "msg", "received_at", "send_by", "status", "create_at", "update_at", "delete_at", "create_by", "update_by", "delete_by", "is_deleted", "broadcast", "remark"}, - []string{}), + modules.Notification, } - - modules.Register(&Notifications) } diff --git a/pkg/mcclient/modules/notify/mod_notification_test.go b/pkg/mcclient/modules/notify/mod_notification_test.go deleted file mode 100644 index 4397e98a7c..0000000000 --- a/pkg/mcclient/modules/notify/mod_notification_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// 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 notify - -// TODO: fix this test -/* -import ( - "testing" - - "yunion.io/x/jsonutils" -) - -func TestNotificationManager(t *testing.T) { - msg := SNotifyMessage{ - Uid: "testuser", - ContactType: []TNotifyChannel{ - NotifyByEmail, NotifyByWebConsole, - }, - Topic: "test message", - Priority: NotifyPriorityNormal, - Msg: "This is a test message. Yey!!", - Remark: "Yunion", - } - msgJson := jsonutils.Marshal(msg) - t.Logf("msg: %s", msgJson) -}*/ diff --git a/pkg/notify/cache/user_group_cache.go b/pkg/notify/cache/user_group_cache.go deleted file mode 100644 index 42886e416e..0000000000 --- a/pkg/notify/cache/user_group_cache.go +++ /dev/null @@ -1,188 +0,0 @@ -// 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 cache - -import ( - "context" - "time" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/errors" - "yunion.io/x/pkg/util/compare" - - "yunion.io/x/onecloud/pkg/cloudcommon/consts" - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" - "yunion.io/x/onecloud/pkg/mcclient/auth" - "yunion.io/x/onecloud/pkg/mcclient/modules" -) - -type SUserGroupCacheManager struct { - db.SResourceBaseManager -} - -type SUserGroup struct { - db.SResourceBase - UserId string - GroupId string - LastCheck time.Time `nullable:"false"` -} - -func (ug *SUserGroup) GetModelManager() db.IModelManager { - return UserGroupCacheManager -} - -var UserGroupCacheManager *SUserGroupCacheManager - -func init() { - UserGroupCacheManager = &SUserGroupCacheManager{db.NewResourceBaseManager( - SUserGroup{}, - "user_group_cache_tbl", - "usergroup", - "usergroups", - )} -} - -func (ug *SUserGroup) IsExpired() bool { - if ug.LastCheck.IsZero() { - return true - } - now := time.Now().UTC() - if ug.LastCheck.Add(consts.GetTenantCacheExpireSeconds()).Before(now) { - return true - } - return false -} - -func (manager *SUserGroupCacheManager) FetchByGroupId(ctx context.Context, groupId string) ([]SUserGroup, error) { - q := manager.Query().Equals("gourp_id", groupId) - ugs := make([]SUserGroup, 0) - err := db.FetchModelObjects(manager, q, &ugs) - if err != nil { - return nil, err - } - var needSync bool - if len(ugs) == 0 { - needSync = true - } - now := time.Now().UTC() - expireTime := now.Add(-consts.GetTenantCacheExpireSeconds()) - for i := range ugs { - if ugs[i].LastCheck.Before(expireTime) { - needSync = true - break - } - } - if !needSync { - return ugs, nil - } - ugs, syncResult, err := manager.Sync(ctx, ugs, groupId) - if err != nil { - return nil, err - } - if syncResult.IsError() { - log.Errorf(syncResult.Result()) - } - return ugs, nil -} - -func (manager *SUserGroupCacheManager) Sync(ctx context.Context, ugCache []SUserGroup, groupId string) ([]SUserGroup, - compare.SyncResult, error) { - lockman.LockRawObject(ctx, manager.KeywordPlural(), groupId) - defer lockman.ReleaseRawObject(ctx, manager.KeywordPlural(), groupId) - - syncResult := compare.SyncResult{} - - // It's to query all groups and their users. - query := jsonutils.NewDict() - query.Set("scope", jsonutils.NewString("system")) - query.Set("system", jsonutils.JSONTrue) - - s := auth.GetAdminSession(ctx, consts.GetRegion(), "v3") - users, err := modules.Groups.GetUsers(s, groupId, query) - if err != nil { - return nil, syncResult, errors.Wrap(err, "fetch users by group id from keystone failed") - } - newUgCache := make([]SUserGroup, len(users.Data)) - for i := range users.Data { - userId, _ := users.Data[i].GetString("id") - newUgCache[i] = SUserGroup{ - UserId: userId, - GroupId: groupId, - } - } - added := make([]SUserGroup, 0) - removed := make([]SUserGroup, 0) - commondb := make([]SUserGroup, 0) - compareSets(ugCache, newUgCache, &added, &removed, &commondb) - now := time.Now().UTC() - for i := range added { - added[i].LastCheck = now - err := manager.TableSpec().Insert(ctx, &added[i]) - if err != nil { - syncResult.AddError(err) - } else { - syncResult.Add() - } - } - - for i := range commondb { - ug := &commondb[i] - _, err := db.Update(ug, func() error { - ug.LastCheck = now - return nil - }) - if err != nil { - syncResult.UpdateError(err) - } else { - syncResult.Update() - } - } - - for i := range removed { - ug := &removed[i] - _, err := db.Update(ug, func() error { - return ug.MarkDelete() - }) - if err != nil { - syncResult.DeleteError(err) - } else { - syncResult.Delete() - } - } - - return newUgCache, syncResult, nil -} - -func compareSets(dbs, remotes []SUserGroup, added, removed, commondb *[]SUserGroup) { - dbmap := make(map[string]SUserGroup) - for i := range dbs { - dbmap[dbs[i].UserId] = dbs[i] - } - - for i := range remotes { - userId := remotes[i].UserId - if _, ok := dbmap[userId]; ok { - *commondb = append(*commondb, remotes[i]) - } else { - *added = append(*added, remotes[i]) - } - delete(dbmap, userId) - } - for _, v := range dbmap { - *removed = append(*removed, v) - } -} diff --git a/pkg/notify/compatible_config.go b/pkg/notify/compatible_config.go deleted file mode 100644 index b2a40c00a2..0000000000 --- a/pkg/notify/compatible_config.go +++ /dev/null @@ -1,128 +0,0 @@ -// 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 notify - -import ( - "context" - "net/http" - - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/appsrv" - "yunion.io/x/onecloud/pkg/httperrors" -) - -const ( - EMAIL_KEYWORDPLURAL = "email_configs" - EMAIL_KEYWORD = "email_config" - EMAIL = "email" - - SMS_KEYWORDPLURAL = "sms_configs" - SMS_KEYWORD = "sms_config" - SMS = "mobile" -) - -func emailConfigDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - // do not need modify - manager, _, _, _ := fetchEnv(ctx, w, r) - params := map[string]string{ - "": EMAIL, - } - err := manager.DeleteConfig(ctx, params) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } -} - -func emailConfigGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, _, query, _ := fetchEnv(ctx, w, r) - params := map[string]string{ - "": EMAIL, - } - ret, err := manager.GetConfig(ctx, params, query) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } - data, _ := ret.Get("config") - dataDict := data.(*jsonutils.JSONDict) - output := jsonutils.NewDict() - output.Add(dataDict, EMAIL_KEYWORD) - appsrv.SendJSON(w, output) -} - -func emailConfigUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, _, _, body := fetchEnv(ctx, w, r) - body, _ = body.Get(EMAIL_KEYWORD) - bodyRet := jsonutils.DeepCopy(body) - newBody := jsonutils.NewDict() - newBody.Add(body, EMAIL) - err := manager.UpdateConfig(ctx, newBody) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } - data := jsonutils.NewDict() - data.Add(jsonutils.NewInt(200), "code") - data.Add(jsonutils.NewString("OK"), "title") - data.Add(bodyRet, "message") - ret := jsonutils.NewDict() - ret.Add(data, EMAIL_KEYWORD) - appsrv.SendJSON(w, ret) -} - -func smsConfigDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - // do not need modify - manager, _, _, _ := fetchEnv(ctx, w, r) - params := map[string]string{ - "": SMS, - } - err := manager.DeleteConfig(ctx, params) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } -} - -func smsConfigGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, _, query, _ := fetchEnv(ctx, w, r) - params := map[string]string{ - "": SMS, - } - ret, err := manager.GetConfig(ctx, params, query) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } - // modify - data, _ := ret.Get("config") - appsrv.SendJSON(w, jsonutils.Marshal(map[string]jsonutils.JSONObject{ - SMS_KEYWORD: data, - })) -} - -func smsConfigUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, _, _, body := fetchEnv(ctx, w, r) - body, _ = body.Get(SMS_KEYWORD) - newBody := jsonutils.NewDict() - newBody.Add(body, SMS) - err := manager.UpdateConfig(ctx, newBody) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } - data := jsonutils.NewDict() - data.Add(jsonutils.NewInt(200), "code") - data.Add(jsonutils.NewString("OK"), "title") - data.Add(body, "message") - ret := jsonutils.NewDict() - ret.Add(data, SMS_KEYWORD) - appsrv.SendJSON(w, ret) -} diff --git a/pkg/notify/dispatcher.go b/pkg/notify/dispatcher.go deleted file mode 100644 index 03d7e23451..0000000000 --- a/pkg/notify/dispatcher.go +++ /dev/null @@ -1,774 +0,0 @@ -// 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 notify - -import ( - "context" - "fmt" - "net/http" - "strings" - "time" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/errors" - "yunion.io/x/pkg/util/sets" - "yunion.io/x/pkg/utils" - "yunion.io/x/sqlchemy" - - "yunion.io/x/onecloud/pkg/appctx" - "yunion.io/x/onecloud/pkg/appsrv" - "yunion.io/x/onecloud/pkg/appsrv/dispatcher" - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" - "yunion.io/x/onecloud/pkg/cloudcommon/policy" - "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/notify/models" - noutils "yunion.io/x/onecloud/pkg/notify/utils" - "yunion.io/x/onecloud/pkg/util/rbacutils" -) - -// NotifyModelDispatcher is designed to complete some function that db.DBModelDispatcher can't. -// The apis of notify module has a certain degree of particularity so that we can't use common function. -type NotifyModelDispatcher struct { - db.DBModelDispatcher -} - -func NewNotifyModelDispatcher(manager db.IModelManager) *NotifyModelDispatcher { - return &NotifyModelDispatcher{*db.NewModelHandler(manager)} -} - -func (self *NotifyModelDispatcher) GetConfig(ctx context.Context, params map[string]string, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { - listResult, err := self.List(ctx, mergeQueryParams(params, query), nil) - if err != nil { - return nil, err - } - configs := jsonutils.NewDict() - for _, data := range listResult.Data { - key, _ := data.GetString("key_text") - value, _ := data.Get("value_text") - configs.Add(value, key) - } - cType, ok := params[""] - if ok { - configs = models.ConfigManager.Database2Display(cType, configs) - } - output := jsonutils.NewDict() - output.Add(configs, models.ConfigManager.Keyword()) - return output, nil -} - -func (self *NotifyModelDispatcher) DeleteConfig(ctx context.Context, params map[string]string) error { - contactType := params[""] - configs, err := models.ConfigManager.GetConfigByType(contactType) - if err != nil { - return errors.Wrap(err, "Get Config by contactType failed") - } - userCred := policy.FetchUserCredential(ctx) - for i := range configs { - err = DeleteItem(&configs[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull) - if err != nil { - return errors.Wrapf(err, "Delete part of old one, so please input new data again.") - } - } - return nil -} - -// UpdateConfig update config and restart corresponding send service. -func (self *NotifyModelDispatcher) UpdateConfig(ctx context.Context, body jsonutils.JSONObject) error { - data := body.(*jsonutils.JSONDict) - contactType := data.SortedKeys()[0] - originData, err := models.ConfigManager.GetConfig(contactType) - if err != nil { - return err - } - tmp, _ := data.Get(contactType) - data = tmp.(*jsonutils.JSONDict) - data = models.ConfigManager.Display2Database(contactType, data) - userCred := policy.FetchUserCredential(ctx) - // If no config of type 'contactType' in database, create news. - // Else delete original ones and create news. - if len(originData) != 0 { - // delete original - configs, err := models.ConfigManager.GetConfigByType(contactType) - if err != nil { - return errors.Wrap(err, "Get Config by contactType failed") - } - for i := range configs { - err = DeleteItem(&configs[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull) - if err != nil { - return errors.Wrapf(err, "Delete part of old one, so please input new data again.") - } - } - } - log.Debugf("update body: %s", data) - keys := data.SortedKeys() - config := make(map[string]string) - createDataList := make([]jsonutils.JSONObject, 0, len(keys)) - - // Extract data - for _, key := range keys { - createData := jsonutils.NewDict() - tmp, _ = data.Get(key) - createData.Add(tmp, "value_text") - createData.Add(jsonutils.NewString(key), "key_text") - createData.Add(jsonutils.NewString(contactType), "type") - createDataList = append(createDataList, createData) - value, _ := tmp.GetString() - config[key] = value - } - - // validate configs - log.Debugf("config: %#v", config) - isValid, message, err := models.NotifyService.ValidateConfig(ctx, contactType, config) - if err != nil { - if errors.Cause(err) != errors.ErrNotImplemented { - return httperrors.NewInternalServerError("Validate Config error: %s", err.Error()) - } - isValid = true - } - if !isValid { - return httperrors.NewInputParameterError("validate failed: %s", message) - } - - // create - for _, createData := range createDataList { - _, err := self.Create(ctx, jsonutils.NewDict(), createData, nil) - if err != nil { - return errors.Wrapf(err, "Create config %s for contact type %s failed", createData.String(), contactType) - } - } - - // update config - models.RestartService(config, contactType) - return nil -} - -func (self *NotifyModelDispatcher) ValidateConfig(ctx context.Context, contactType string, body jsonutils.JSONObject) error { - dict, ok := body.(*jsonutils.JSONDict) - if !ok { - return httperrors.NewInputParameterError("") - } - dict = models.ConfigManager.Display2Database(contactType, dict) - configs := make(map[string]string) - for _, key := range dict.SortedKeys() { - value, err := dict.GetString(key) - if err != nil { - return errors.Wrap(err, "jsonutils.JsonDict.GetString") - } - configs[key] = value - } - isValid, message, err := models.NotifyService.ValidateConfig(ctx, contactType, configs) - if err != nil { - if errors.Cause(err) == errors.ErrNotImplemented { - return httperrors.NewNotImplementedError("validating config of %s", contactType) - } - return err - } - if isValid == false { - return httperrors.NewInputParameterError(message) - } - return nil -} - -// CreateNotification create new notifications and send them through rpc.RpcService. -// If data contains 'gid' field, that means that send message to all users in group. -// Else send messager to user whose uid equals 'uid' in data. -func (self *NotifyModelDispatcher) CreateNotification(ctx context.Context, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - // Get all contacts info of group if data contains "gid". - // If no contact, return ErrContactNotFound. - contactType, _ := data.GetString("contact_type") - group := false - var ids []string - if data.Contains("gid") { - group = true - ids = self.getIds(data, "gid") - } else { - ids = self.getIds(data, "uid") - } - contacts, err := models.ContactManager.GetAllNotify(ctx, ids, contactType, group) - if err != nil { - return nil, httperrors.NewGeneralError(errors.Wrap(err, "get all contacts error")) - } - - notificationIDs, err := models.NotificationManager.BatchCreate(ctx, data, contacts) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - ret := jsonutils.NewDict() - ret.Add(jsonutils.NewStringArray(notificationIDs), "notifications") - return ret, nil -} - -func (self *NotifyModelDispatcher) getIds(data jsonutils.JSONObject, key string) []string { - var ids []string - tmpIds, err := data.GetArray(key) - if err != nil { - id, _ := data.GetString(key) - ids = make([]string, 1) - ids[0] = id - } else { - ids = noutils.JsonArrayToStringArray(tmpIds) - } - // remove entry in which empty content - ret := make([]string, 0, len(ids)) - for _, id := range ids { - if len(id) == 0 { - continue - } - ret = append(ret, id) - } - return ret -} - -// Verify process: -// 1.fetch verify by ID; 2.check that if verify is expired; -// 3.if not check that if token is correct and update status of contact whose id is verify's CID -// 4.otherwise generate a new verify and delete old one -func (self *NotifyModelDispatcher) Verify(ctx context.Context, params map[string]string, query jsonutils.JSONObject) error { - processID := params[""] - token, _ := query.GetString("token") - manager := models.VerifyManager - verifys, err := manager.FetchByID(processID) - if err != nil { - return httperrors.NewGeneralError(err) - } - var verifition models.SVerify - if len(verifys) == 0 { - return httperrors.NewNotFoundError("%s verify record not found", processID) - } - current, have := time.Now(), false - for i := range verifys { - if current.Before(verifys[i].ExpireAt) { - verifition = verifys[i] - have = true - break - } - } - if !have { - return httperrors.NewBadRequestError(models.VERIFICATION_TOKEN_EXPIRED) - } - if verifition.Token != token { - return httperrors.NewBadRequestError(models.VERIFICATION_TOKEN_INVALID) - } - // modify contact's status and verified time. - data := jsonutils.NewDict() - data.Set("status", jsonutils.NewString(models.VERIFICATION_VERIFIED)) - data.Set("verified_at", jsonutils.NewTimeString(current)) - _, err = self.Update(ctx, verifition.CID, jsonutils.NewDict(), data, nil) - if err != nil { - return httperrors.NewGeneralError(err) - } - return nil -} - -// VerifyTrigger process: -// 1.fetch contact by the information in data -// 2.if contact'status is 'init', make a new verify and send a verify message to the contact adress -// 3.if contact'status is 'verifying', fetch verify by CID, generate a new verify if it has expired -// or return a error mention that "please don't try again". -func (self *NotifyModelDispatcher) VerifyTrigger(ctx context.Context, params map[string]string, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - uid := params[""] - contact, _ := data.GetString("contact") - contactType, _ := data.GetString("contact_type") - contacts, err := models.ContactManager.FetchByMore(uid, contact, contactType) - if err != nil || len(contacts) == 0 { - return nil, errors.Error(fmt.Sprintf("uid '%s' don't have contact '%s' of contact_type '%s'", uid, contact, contactType)) - } - userCred := policy.FetchUserCredential(ctx) - scontact := contacts[0] - - makeNewVerify := func() (jsonutils.JSONObject, error) { - verification := models.NewSVerify(contactType, scontact.ID) - err = models.VerifyManager.Create(ctx, userCred, verification) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - // update contact state - if scontact.Status != models.CONTACT_VERIFYING { - scontact.SetStatus(userCred, models.CONTACT_VERIFYING, "") - } - - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - processID := verification.ID - err := models.SendVerifyMessage(ctx, userCred, verification, &scontact) - if err != nil { - return nil, err - } - ret := map[string]map[string]string{ - "contact": { - "process_id": processID, - }, - } - return jsonutils.Marshal(ret), nil - } - if scontact.Status == models.CONTACT_INIT { - return makeNewVerify() - } - if scontact.Status == models.CONTACT_VERIFYING { - verifications, err := models.VerifyManager.FetchByCID(scontact.ID, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { - q = q.In("status", []string{models.VERIFICATION_SENT, "init"}).Desc("created_at") - return q - }) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - if len(verifications) == 0 { - // no verifications in status "sent" - return makeNewVerify() - } - current := time.Now() - for _, verification := range verifications { - if current.After(verification.ExpireAt) { - //delete old one - err = DeleteItem(&verification, ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - return makeNewVerify() - } - } - return nil, httperrors.NewGeneralError(models.ErrVeritying) - } - - return jsonutils.JSONNull, nil -} - -// DeleteContacts delete a group of contacts -func (self *NotifyModelDispatcher) DeleteContacts(ctx context.Context, uidArray []jsonutils.JSONObject) error { - // Get all id of uid - uids := make([]string, len(uidArray)) - log.Debugf("uidArray: %s", uidArray) - for i := range uidArray { - uids[i], _ = uidArray[i].GetString() - } - log.Debugf("uids: %#v", uids) - uname := false - if v := ctx.Value("uname"); v != nil { - uname = true - } - contacts, err := models.ContactManager.FetchByUIDs(ctx, uids, uname) - if err != nil { - return httperrors.NewGeneralError(err) - } - userCred := policy.FetchUserCredential(ctx) - deleteFailed := make([]string, 0, 1) - for _, contact := range contacts { - err = DeleteItem(&contact, ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull) - if err != nil { - deleteFailed = append(deleteFailed, contact.ID) - } - } - // clean cache - noutils.DeleteUsers(ctx, userCred, uids) - if len(deleteFailed) != 0 { - errInfo := strings.Join(deleteFailed, ", ") + " ; these contact delete failed." - return errors.Error(errInfo) - } - return nil -} - -// UpdateContacts analysis the data and update corresponding contacts if they exist in the database create new ones. -func (self *NotifyModelDispatcher) UpdateContacts(ctx context.Context, idstr string, query jsonutils.JSONObject, - datas []jsonutils.JSONObject, pullCtypes []jsonutils.JSONObject, ctxIds []dispatcher.SResourceContext) (jsonutils.JSONObject, - error) { - - type pair struct { - contact string - enabled string - } - - // contactInfos will be used to find all contact info need to update. - // And others will be created. - contactInfos := make(map[string]pair) - contactTypes := make([]string, len(datas)) - for i := range datas { - contactType, _ := datas[i].GetString("contact_type") - if _, ok := models.UpdateNotAllow[contactType]; ok { - continue - } - contact, _ := datas[i].GetString("contact") - enabled := "-1" - if datas[i].Contains("enabled") { - enabled, _ = datas[i].GetString("enabled") - } - contactInfos[contactType] = pair{contact, enabled} - contactTypes[i] = contactType - } - - records, err := models.ContactManager.FetchByUIDAndCType(idstr, contactTypes) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - - // updateFailed record the information of failed update - updateFailed := make([]string, 0, 1) - deleteFailed := make([]string, 0, 1) - // UpdateItem contact info - userCred := policy.FetchUserCredential(ctx) - for i := range records { - contactType := records[i].ContactType - pairUpdate := contactInfos[contactType] - if len(pairUpdate.contact) == 0 { - // delete - err = DeleteItem(&records[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull) - if err != nil { - deleteFailed = append(deleteFailed, fmt.Sprintf(`uid:%q, contact_type:%q`, idstr, contactType)) - } - continue - } - updateData := jsonutils.NewDict() - updateData.Set("contact", jsonutils.NewString(pairUpdate.contact)) - if pairUpdate.enabled != "-1" { - updateData.Set("enabled", jsonutils.NewString(pairUpdate.enabled)) - } - if records[i].Contact != pairUpdate.contact { - updateData.Set("status", jsonutils.NewString(models.CONTACT_INIT)) - } - // update is not relational - //updateData.Set("status", jsonutils.NewString("init")) - err = UpdateItem(models.ContactManager, &records[i], ctx, userCred, jsonutils.JSONNull, updateData) - if err != nil { - updateFailed = append(updateFailed, fmt.Sprintf(`uid:%q, contact_type:%q, contact:%q`, idstr, contactType, pairUpdate.contact)) - } - delete(contactInfos, contactType) - } - - // createFailed record the information of failed creation - createFailed := make([]string, 0, 1) - // Create contact info - type CreateData struct { - UID string - ContactType string - Contact string - Enabled string - } - newDatas := make([]CreateData, 0, len(contactInfos)) - for conType, conPair := range contactInfos { - tmp := CreateData{ - UID: idstr, - ContactType: conType, - Contact: conPair.contact, - } - if conPair.enabled != "-1" { - tmp.Enabled = conPair.enabled - } - newDatas = append(newDatas, tmp) - } - - // Enable closing feishu and dingtalk notify. - // Temporary solution for 3.2. - allPullType := sets.NewString(models.FEISHU, models.DINGTALK) - - pulls := make([]string, len(pullCtypes)) - for i := range pullCtypes { - pulls[i], _ = pullCtypes[i].GetString() - allPullType.Delete(pulls[i]) - } - - // delete first - records, err = models.ContactManager.FetchByUIDAndCType(idstr, allPullType.UnsortedList()) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - for i := range records { - err := DeleteItem(&records[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull) - if err != nil { - deleteFailed = append(deleteFailed, fmt.Sprintf(`uid:%q, contact_type:%q`, idstr, records[i].ContactType)) - } - } - - if len(pulls) > 0 { - contacts, err := models.ContactManager.FetchByUIDAndCType(idstr, pulls) - if err != nil { - return nil, err - } - set := sets.NewString(pulls...) - for i := range contacts { - set.Delete(contacts[i].ContactType) - } - for _, ct := range set.UnsortedList() { - tmp := CreateData{ - UID: idstr, - ContactType: ct, - Enabled: "1", - Contact: "user_id", - } - newDatas = append(newDatas, tmp) - } - } - log.Debugf("newDatas: %s", newDatas) - - for _, newData := range newDatas { - _, err := self.Create(ctx, jsonutils.NewDict(), jsonutils.Marshal(newData), ctxIds) - if err != nil { - createFailed = append(createFailed, fmt.Sprintf(`uid:%q, contact_type:%q, contact:%q`, idstr, - newData.ContactType, newData.Contact)) - } - } - - // generate error through updateFailed and createFailed - if len(updateFailed) != 0 || len(createFailed) != 0 || len(deleteFailed) != 0 { - var errInfoBuffer strings.Builder - if len(updateFailed) != 0 { - errInfoBuffer.WriteString(strings.Join(updateFailed, "; ")) - errInfoBuffer.WriteString(" update failed. ") - } - if len(deleteFailed) != 0 { - errInfoBuffer.WriteString(strings.Join(updateFailed, "; ")) - errInfoBuffer.WriteString(" delete failed. ") - } - if len(createFailed) != 0 { - errInfoBuffer.WriteString(strings.Join(createFailed, "; ")) - errInfoBuffer.WriteString(" create failed. ") - } - errInfo := errInfoBuffer.String() - return nil, httperrors.NewGeneralError(errors.Error(errInfo)) - } - - models.PullContact(idstr, pulls) - - // keep the return value same as this of the GET interface - ret := jsonutils.NewDict() - contacts, err := models.ContactManager.FetchByUIDs(ctx, []string{idstr}, false) - if err != nil { - log.Errorf("fetch contact %s: %v", idstr, err) - return ret, nil - } - if len(contacts) == 0 { - return nil, nil - } - contact := contacts[0] - outDetails, err := contact.GetExtraDetails(ctx, userCred, ret, false) - if err != nil { - log.Errorf("fetch contact details %s(%s): %v", - contact.GetName(), contact.GetId(), err) - return ret, nil - } - out := jsonutils.Marshal(outDetails) - out.(*jsonutils.JSONDict).Set("created_at", jsonutils.NewString(contact.CreatedAt.String())) - return out, nil -} - -func (self *NotifyModelDispatcher) UpdateTemplate(ctx context.Context, ctype string, query jsonutils.JSONObject, - datas []jsonutils.JSONObject) error { - - type sTemplate struct { - ContactType string - Topic string - TemplateType string - Content string - } - templates := make([]sTemplate, 0, len(datas)) - topics := sets.NewString() - for _, data := range datas { - var tem sTemplate - err := data.Unmarshal(&tem) - if err != nil { - return errors.Wrap(err, "data.Unmarshal") - } - if tem.TemplateType != models.TEMPLATE_TYPE_REMOTE && tem.TemplateType != models. - TEMPLATE_TYPE_CONTENT && tem.TemplateType != models.TEMPLATE_TYPE_TITLE { - - return httperrors.NewInputParameterError("no support for such template type '%s'", tem.TemplateType) - } - tem.ContactType = ctype - tem.Topic = strings.ToUpper(tem.Topic) - templates = append(templates, tem) - topics.Insert(tem.Topic) - } - - q := models.TemplateManager.Query().Equals("contact_type", ctype).In("topic", topics.List()) - templateModels := make([]models.STemplate, 0, 1) - err := db.FetchModelObjects(models.TemplateManager, q, &templateModels) - if err != nil { - log.Errorf("db.FetchModelObjects sql: %s", q.String()) - return errors.Wrap(err, "db.FetchModelObjects") - } - - templateMaps := make(map[string]*models.STemplate) - for i := range templateModels { - k := fmt.Sprintf("%s/%s/%s", templateModels[i].ContactType, templateModels[i].Topic, templateModels[i].TemplateType) - templateMaps[k] = &templateModels[i] - } - - userCred := policy.FetchUserCredential(ctx) - for _, tem := range templates { - k := fmt.Sprintf("%s/%s/%s", tem.ContactType, tem.Topic, tem.TemplateType) - if tmod, ok := templateMaps[k]; ok { - updateData := jsonutils.NewDict() - updateData.Add(jsonutils.NewString(tem.Content), "content") - err = UpdateItem(models.TemplateManager, tmod, ctx, userCred, query, updateData) - if err != nil { - return errors.Wrapf(err, "fail to update template '%s'", tmod.ID) - } - continue - } - _, err = self.Create(ctx, query, jsonutils.Marshal(tem), nil) - if err != nil { - return errors.Wrapf(err, "fail to create template(contact_type: %s, topic: %s, template_type: %s)", - tem.ContactType, tem.Topic, tem.TemplateType) - } - } - return nil -} - -func (self *NotifyModelDispatcher) DeleteTemplate(ctx context.Context, query jsonutils.JSONObject, ctype, topic string) error { - - q := models.TemplateManager.Query().Equals("contact_type", ctype) - if len(topic) != 0 { - q = q.Equals("topic", topic) - } - templates := make([]models.STemplate, 0, 1) - err := db.FetchModelObjects(models.TemplateManager, q, &templates) - if err != nil { - log.Errorf("db.FetchModelObjects sql: %s", q.String()) - return errors.Wrap(err, "db.FetchModelObjects") - } - userCred := policy.FetchUserCredential(ctx) - for i := range templates { - err = DeleteItem(&templates[i], ctx, userCred, query, jsonutils.JSONNull) - if err != nil { - return errors.Wrapf(err, "fail to delete template '%s'", templates[i].ID) - } - } - return nil -} - -// fetchEnv fetch handler, params, query and body from ctx(context.Context) -func fetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (*NotifyModelDispatcher, map[string]string, jsonutils.JSONObject, jsonutils.JSONObject) { - params, query, body := appsrv.FetchEnv(ctx, w, r) - metadata := appctx.AppContextMetadata(ctx) - manager, ok := metadata["manager"].(*NotifyModelDispatcher) - if !ok { - log.Fatalf("No manager found for URL: %s", r.URL) - } - return manager, params, query, body -} - -func mergeQueryParams(params map[string]string, query jsonutils.JSONObject, excludes ...string) jsonutils.JSONObject { - if query == nil { - query = jsonutils.NewDict() - } - queryDict := query.(*jsonutils.JSONDict) - for k, v := range params { - if !utils.IsInStringArray(k, excludes) { - queryDict.Add(jsonutils.NewString(v), k[1:len(k)-1]) - } - } - return queryDict -} - -// DeleteItem delete a database record corresponding to model -func DeleteItem(model db.IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { - lockman.LockObject(ctx, model) - defer lockman.ReleaseObject(ctx, model) - err := model.ValidateDeleteCondition(ctx) - if err != nil { - log.Errorf("validate delete condition error: %s", err) - return err - } - err = db.CustomizeDelete(model, ctx, userCred, query, data) - if err != nil { - log.Errorf("customize delete error: %s", err) - return httperrors.NewNotAcceptableError("%v", err) - } - model.PreDelete(ctx, userCred) - err = model.Delete(ctx, userCred) - if err != nil { - log.Errorf("Delete error %s", err) - return err - } - model.PostDelete(ctx, userCred) - return nil -} - -// UpdateItem update a database record corresponding to model whose update fields are in data -func UpdateItem(manager db.IModelManager, item db.IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { - lockman.LockObject(ctx, item) - defer lockman.ReleaseObject(ctx, item) - var err error - err = item.ValidateUpdateCondition(ctx) - - if err != nil { - log.Errorf("validate update condition error: %s", err) - return httperrors.NewGeneralError(err) - } - - dataDict, ok := data.(*jsonutils.JSONDict) - if !ok { - return httperrors.NewInternalServerError("Invalid data JSONObject") - } - - dataDict, err = db.ValidateUpdateData(item, ctx, userCred, query, dataDict) - if err != nil { - errMsg := fmt.Sprintf("validate update data error: %s", err) - log.Errorf(errMsg) - return httperrors.NewGeneralError(err) - } - - item.PreUpdate(ctx, userCred, query, dataDict) - - diff, err := db.Update(item, func() error { - filterData := dataDict.CopyIncludes(updateFields(manager, userCred)...) - err = filterData.Unmarshal(item) - if err != nil { - errMsg := fmt.Sprintf("unmarshal fail: %s", err) - log.Errorf(errMsg) - return httperrors.NewGeneralError(err) - } - return nil - }) - - if err != nil { - log.Errorf("save update error: %s", err) - return httperrors.NewGeneralError(err) - } - db.OpsLog.LogEvent(item, db.ACT_UPDATE, diff, userCred) - - item.PostUpdate(ctx, userCred, query, data) - - return nil - -} - -// get the field of model which is d -func updateFields(manager db.IModelManager, userCred mcclient.TokenCredential) []string { - ret := make([]string, 0) - for _, col := range manager.TableSpec().Columns() { - tags := col.Tags() - update := tags["update"] - if allowAction(manager, userCred, update, db.IsAllowUpdate) { - ret = append(ret, col.Name()) - } - } - return ret -} - -func allowAction(manager db.IResource, userCred mcclient.TokenCredential, action string, testfunc func(scope rbacutils.TRbacScope, userCred mcclient.TokenCredential, manager db.IResource) bool) bool { - if action == "user" { - return true - } - if action == "domain" && (testfunc(rbacutils.ScopeDomain, userCred, manager) || testfunc(rbacutils.ScopeSystem, userCred, manager)) { - return true - } - if action == "admin" && testfunc(rbacutils.ScopeSystem, userCred, manager) { - return true - } - return false -} diff --git a/pkg/notify/handlers.go b/pkg/notify/handlers.go deleted file mode 100644 index 71485c9b4c..0000000000 --- a/pkg/notify/handlers.go +++ /dev/null @@ -1,450 +0,0 @@ -// 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 notify - -import ( - "context" - "fmt" - "net/http" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/errors" - - "yunion.io/x/onecloud/pkg/appctx" - "yunion.io/x/onecloud/pkg/appsrv" - "yunion.io/x/onecloud/pkg/cloudcommon/consts" - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/cloudcommon/policy" - "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/mcclient/auth" - "yunion.io/x/onecloud/pkg/mcclient/modulebase" - "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/notify/cache" - "yunion.io/x/onecloud/pkg/notify/models" - "yunion.io/x/onecloud/pkg/notify/options" - "yunion.io/x/onecloud/pkg/notify/utils" -) - -var API_VERSION = "api/v1" - -func InitHandlers(app *appsrv.Application) { - // add version handler with API_VERSION prefix - app.AddDefaultHandler("GET", API_VERSION+"/version", appsrv.VersionHandler, "version") - db.AddScopeResourceCountHandler(API_VERSION, app) - - db.RegisterModelManager(models.ContactManager) - db.RegisterModelManager(models.VerifyManager) - db.RegisterModelManager(models.NotificationManager) - db.RegisterModelManager(models.ConfigManager) - db.RegisterModelManager(cache.UserCacheManager) - db.RegisterModelManager(cache.UserGroupCacheManager) - db.RegisterModelManager(models.TemplateManager) - AddNotifyDispatcher(API_VERSION, app) -} - -// Middleware -func middleware(f appsrv.FilterHandler) appsrv.FilterHandler { - hander := func(ctx context.Context, w http.ResponseWriter, r *http.Request) { - if _, ok := r.URL.Query()["uname"]; ok { - // Uname - log.Debugf("url.Query has a uname") - params := appctx.AppContextParams(ctx) - if uid, ok := params[""]; ok { - userDetail, err := utils.GetUserByIDOrName(ctx, uid) - if err != nil { - httperrors.NotFoundError(ctx, w, "Uid or Uname Not Found") - return - } - log.Debugf("find userDetail, id: %s, name: %s", userDetail.Id, userDetail.Name) - params[""] = userDetail.Id - } - ctx = context.WithValue(ctx, "uname", true) - } - f(ctx, w, r) - } - if consts.IsRbacEnabled() { - return auth.AuthenticateWithDelayDecision(hander, true) - } else { - return auth.Authenticate(hander) - } -} - -func AddNotifyDispatcher(prefix string, app *appsrv.Application) { - var metadata map[string]interface{} - var tags map[string]string - - // Contact Handler - modelDispatcher := NewNotifyModelDispatcher(models.ContactManager) - metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()} - app.AddHandler2("POST", - fmt.Sprintf("%s/%s//update-contact", prefix, modelDispatcher.KeywordPlural()), - middleware(contactUpdateHandler), metadata, "contact_update", tags) - // List - app.AddHandler2("GET", - fmt.Sprintf("%s/%s", prefix, modelDispatcher.KeywordPlural()), - middleware(listHandler), metadata, "list_contacts", tags) - - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/users", prefix, modelDispatcher.KeywordPlural()), - middleware(keyStoneUserListHandler), metadata, "list_users", tags) - - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()), - middleware(getHandler), metadata, "list_by_uid", tags) - - app.AddHandler2("POST", - fmt.Sprintf("%s/%s/delete-contact", prefix, modelDispatcher.KeywordPlural()), - middleware(deleteContactHandler), metadata, "delete", tags) - - // verify-trigger - app.AddHandler2("POST", - fmt.Sprintf("%s/%s//verify", prefix, modelDispatcher.KeywordPlural()), - middleware(verifyTriggerHandler), metadata, "verify_trigger", tags) - - // Verify Handler, this modelDispatcher need db.DBModelDispatcher'Create function to create Contact so this modelDispatcher is - // NotifyModelDispatcher whose DBModelDispatcher has modelManager models.ContactManager - metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": models.VerifyManager.KeywordPlural()} - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/", prefix, models.VerifyManager.KeywordPlural()), - middleware(verifyHandler), metadata, "verify", tags) - - // notification Handler - modelDispatcher = NewNotifyModelDispatcher(models.NotificationManager) - metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()} - app.AddHandler2("POST", - fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()), - middleware(notificationHandler), metadata, "send_notifications", tags) - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()), - middleware(listHandler), metadata, "send_notifications", tags) - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()), - middleware(listHandler), metadata, "list_notification_by_id", tags) - - // config Handler - modelDispatcher = NewNotifyModelDispatcher(models.ConfigManager) - metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()} - app.AddHandler2("POST", - fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()), - middleware(configUpdateHandler), metadata, "update_configs", tags) - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()), - middleware(configGetHandler), metadata, "get_configs", tags) - app.AddHandler2("DELETE", - fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()), - middleware(configDeleteHandler), metadata, "delete_configs", tags) - app.AddHandler2("POST", - fmt.Sprintf("%s/%s//validate", prefix, modelDispatcher.KeywordPlural()), - middleware(configValidateHandler), metadata, "validate_configs", tags) - - // email handler for being compatible - app.AddHandler2("POST", - fmt.Sprintf("%s/%s/", prefix, EMAIL_KEYWORDPLURAL), - middleware(emailConfigUpdateHandler), metadata, "", tags) - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/", prefix, EMAIL_KEYWORDPLURAL), - middleware(emailConfigGetHandler), metadata, "", tags) - app.AddHandler2("DELETE", - fmt.Sprintf("%s/%s/", prefix, EMAIL_KEYWORDPLURAL), - middleware(emailConfigDeleteHandler), metadata, "", tags) - app.AddHandler2("PUT", - fmt.Sprintf("%s/%s/", prefix, EMAIL_KEYWORDPLURAL), - middleware(emailConfigUpdateHandler), metadata, "", tags) - - app.AddHandler2("POST", - fmt.Sprintf("%s/%s/", prefix, SMS_KEYWORDPLURAL), - middleware(smsConfigUpdateHandler), metadata, "", tags) - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/", prefix, SMS_KEYWORDPLURAL), - middleware(smsConfigGetHandler), metadata, "", tags) - app.AddHandler2("DELETE", - fmt.Sprintf("%s/%s/", prefix, SMS_KEYWORDPLURAL), - middleware(smsConfigDeleteHandler), metadata, "", tags) - app.AddHandler2("PUT", - fmt.Sprintf("%s/%s/", prefix, SMS_KEYWORDPLURAL), - middleware(smsConfigUpdateHandler), metadata, "", tags) - - // Template Handler - modelDispatcher = NewNotifyModelDispatcher(models.TemplateManager) - metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()} - app.AddHandler2("POST", - fmt.Sprintf("%s/%s//update-template", prefix, modelDispatcher.KeywordPlural()), - middleware(templateUpdateHandler), metadata, "update_template", tags) - // List - app.AddHandler2("GET", - fmt.Sprintf("%s/%s", prefix, modelDispatcher.KeywordPlural()), - middleware(listHandler), metadata, "list_template", tags) - - app.AddHandler2("POST", - fmt.Sprintf("%s/%s/delete-template", prefix, modelDispatcher.KeywordPlural()), - middleware(deleteTemplateHandler), metadata, "delete", tags) -} - -func templateUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, params, query, body := fetchEnv(ctx, w, r) - data, err := body.GetArray(manager.Keyword(), manager.KeywordPlural()) - if err != nil { - httperrors.GeneralServerError(ctx, w, httperrors.NewInputParameterError("need %s and %s", - manager.Keyword(), manager.KeywordPlural())) - return - } - ctype := params[""] - if len(ctype) == 0 { - httperrors.InputParameterError(ctx, w, "ctype of template should not be empty") - } - err = manager.UpdateTemplate(ctx, ctype, mergeQueryParams(params, query), data) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } -} - -func deleteTemplateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, _, query, body := fetchEnv(ctx, w, r) - if !body.Contains("contact_type") { - httperrors.InputParameterError(ctx, w, "miss contact_type") - return - } - ctype, _ := body.GetString("contact_type") - topic, _ := body.GetString("topic") - err := manager.DeleteTemplate(ctx, query, ctype, topic) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } -} - -func configDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, params, _, _ := fetchEnv(ctx, w, r) - err := manager.DeleteConfig(ctx, params) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } -} - -func configGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, params, query, _ := fetchEnv(ctx, w, r) - ret, err := manager.GetConfig(ctx, params, query) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - return - } - appsrv.SendJSON(w, ret) -} - -func configUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, _, _, body := fetchEnv(ctx, w, r) - body, err := body.Get(models.ConfigManager.Keyword()) - if err != nil { - httperrors.GeneralServerError(ctx, w, httperrors.NewInputParameterError("need config or configs")) - return - } - err = manager.UpdateConfig(ctx, body) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } -} - -func configValidateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, params, _, body := fetchEnv(ctx, w, r) - body, err := body.Get(models.ConfigManager.Keyword()) - if err != nil { - httperrors.GeneralServerError(ctx, w, httperrors.NewInputParameterError("need config")) - } - ctype := params[""] - err = manager.ValidateConfig(ctx, ctype, body) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - return - } -} - -func notificationHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, _, _, body := fetchEnv(ctx, w, r) - data, err := body.Get(manager.Keyword()) - if err != nil { - httperrors.BadRequestError(ctx, w, "request body should contain %s", manager.Keyword()) - return - } - if !data.Contains("gid") && !data.Contains("uid") { - httperrors.MissingParameterError(ctx, w, "gid | uid") - return - } - _, err = manager.CreateNotification(ctx, data) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - return - } -} - -// verify handler -func verifyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, params, query, _ := fetchEnv(ctx, w, r) - err := manager.Verify(ctx, params, query) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - } -} - -// contact update handler -func contactUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, params, query, body := fetchEnv(ctx, w, r) - - var data []jsonutils.JSONObject - data, err := body.GetArray(manager.Keyword(), manager.KeywordPlural()) - if err != nil { - log.Errorf("body: %s, err: %s\n", body.String(), err) - httperrors.GeneralServerError(ctx, w, httperrors.NewInputParameterError("need %s and %s", - manager.Keyword(), manager.KeywordPlural())) - return - } - log.Debugf("data: %s", data) - pullCtypes, _ := body.GetArray(manager.Keyword(), "pull") - log.Debugf("pullCtypes: %s", pullCtypes) - - uid := params[""] - out, err := manager.UpdateContacts(ctx, uid, mergeQueryParams(params, query), data, pullCtypes, nil) - if err != nil { - log.Errorf(err.Error()) - httperrors.BadRequestError(ctx, w, "") - return - } - if out == nil { - return - } - appsrv.SendJSON(w, wrap(out, manager.Keyword())) -} - -// delete contact handler -func deleteContactHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, _, _, body := fetchEnv(ctx, w, r) - - var data []jsonutils.JSONObject - var err error - if body != nil { - data, err = body.GetArray(manager.KeywordPlural()) - if err != nil { - httperrors.BadRequestError(ctx, w, "request body should have %s", manager.KeywordPlural()) - return - } - } - err = manager.DeleteContacts(ctx, data) - - if err != nil { - log.Errorf("delete contact of %s failed, error: %s", data, err) - httperrors.GeneralServerError(ctx, w, errors.Error("delete failed")) - } -} - -// verify trigger handler -func verifyTriggerHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, params, _, body := fetchEnv(ctx, w, r) - data, err := body.Get(models.ContactManager.Keyword()) - if err != nil { - httperrors.BadRequestError(ctx, w, "request body should have %s", manager.Keyword()) - return - } - ret, err := manager.VerifyTrigger(ctx, params, data) - if err != nil { - log.Errorf("verifyTrigger failed beacause %s", err) - httperrors.GeneralServerError(ctx, w, err) - return - } - appsrv.SendJSON(w, ret) -} - -// list handler for all resource in notify module -func listHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, params, query, _ := fetchEnv(ctx, w, r) - listResult, err := manager.List(ctx, mergeQueryParams(params, query), nil) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - return - } - appsrv.SendJSON(w, modulebase.ListResult2JSONWithKey(listResult, manager.KeywordPlural())) -} - -func getHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, params, query, _ := fetchEnv(ctx, w, r) - listResult, err := manager.List(ctx, mergeQueryParams(params, query), nil) - if err != nil { - httperrors.GeneralServerError(ctx, w, err) - return - } - var data jsonutils.JSONObject - if len(listResult.Data) == 0 { - data = jsonutils.NewDict() - } else { - data = listResult.Data[0] - } - appsrv.SendJSON(w, wrap(data, manager.Keyword())) -} - -func wrap(data jsonutils.JSONObject, key string) jsonutils.JSONObject { - ret := jsonutils.NewDict() - ret.Add(data, key) - return ret -} - -// offset ang limit is not useable for here -func keyStoneUserListHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - manager, _, query, _ := fetchEnv(ctx, w, r) - haveContacts := jsonutils.QueryBoolean(query, "have_contacts", false) - - userCred := policy.FetchUserCredential(ctx) - s := auth.GetSession(ctx, userCred, options.Options.Region, "") - users, err := modules.UsersV3.List(s, query) - if err != nil { - log.Errorf("keystone list error: %s", err) - httperrors.InternalServerError(ctx, w, err.Error()) - return - } - q := models.ContactManager.Query("uid").GroupBy("uid") - row, err := q.Rows() - if err != nil { - log.Errorf("get contact's uid error: %s", err) - httperrors.InternalServerError(ctx, w, err.Error()) - return - } - defer row.Close() - uidSet, uid := make(map[string]struct{}), "" - for row.Next() { - row.Scan(&uid) - uidSet[uid] = struct{}{} - } - - type sPair struct { - ID string - Name string - } - newDatas := make([]sPair, 0, len(users.Data)) - - for _, data := range users.Data { - id, _ := data.GetString("id") - name, _ := data.GetString("name") - if _, ok := uidSet[id]; ok { - if haveContacts { - newDatas = append(newDatas, sPair{id, name}) - } - continue - } - if haveContacts { - continue - } - newDatas = append(newDatas, sPair{id, name}) - } - ret := jsonutils.NewDict() - ret.Add(jsonutils.Marshal(newDatas), manager.Keyword()) - appsrv.SendJSON(w, ret) -} diff --git a/pkg/notify/interface/interface.go b/pkg/notify/interface.go similarity index 91% rename from pkg/notify/interface/interface.go rename to pkg/notify/interface.go index 21d72105e2..5f24369a28 100644 --- a/pkg/notify/interface/interface.go +++ b/pkg/notify/interface.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package _interface +package notify import ( "context" @@ -28,6 +28,7 @@ type INotifyService interface { RestartService(ctx context.Context, config SConfig, serviceName string) Send(ctx context.Context, contactType, contact, topic, msg, priority string) error ContactByMobile(ctx context.Context, mobile, serviceName string) (string, error) + BatchSend(ctx context.Context, contacts []string, contactType, topic, message, priority string) ([]*apis.FailedRecord, error) ValidateConfig(ctx context.Context, cType string, configs map[string]string) (isValid bool, message string, err error) } diff --git a/pkg/notify/models/config.go b/pkg/notify/models/config.go new file mode 100644 index 0000000000..c7eec3d823 --- /dev/null +++ b/pkg/notify/models/config.go @@ -0,0 +1,296 @@ +// 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 models + +import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + notifyv2 "yunion.io/x/onecloud/pkg/notify" + "yunion.io/x/onecloud/pkg/notify/oldmodels" + "yunion.io/x/onecloud/pkg/notify/options" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SConfigManager struct { + db.SStandaloneResourceBaseManager +} + +var ConfigManager *SConfigManager + +func init() { + ConfigManager = &SConfigManager{ + SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( + SConfig{}, + "configs_tbl", + "notifyconfig", + "notifyconfigs", + ), + } + ConfigManager.SetVirtualObject(ConfigManager) +} + +type SConfig struct { + db.SStandaloneResourceBase + + Type string `width:"15" nullable:"false" create:"required" get:"admin" list:"admin"` + Content jsonutils.JSONObject `nullable:"false" create:"required" update:"admin" get:"admin" list:"admin"` +} + +func (cm *SConfigManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ConfigCreateInput) (api.ConfigCreateInput, error) { + var err error + input.StandaloneResourceCreateInput, err = cm.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StandaloneResourceCreateInput) + if err != nil { + return input, err + } + if !utils.IsInStringArray(input.Type, []string{api.EMAIL, api.MOBILE, api.DINGTALK, api.FEISHU, api.WEBCONSOLE, api.WORKWX, api.FEISHU_ROBOT, api.DINGTALK_ROBOT, api.WORKWX_ROBOT}) { + return input, httperrors.NewInputParameterError("unkown type %q", input.Type) + } + if input.Content == nil { + return input, httperrors.NewMissingParameterError("content") + } + config, err := cm.GetConfigByType(input.Type) + if err == nil && config != nil { + return input, httperrors.NewDuplicateResourceError("duplicate type %q", input.Type) + } + if err != nil && errors.Cause(err) != sql.ErrNoRows { + return input, err + } + // validate + configs := make(map[string]string) + err = input.Content.Unmarshal(&configs) + if err != nil { + return input, err + } + isValid, message, err := NotifyService.ValidateConfig(ctx, input.Type, configs) + if err != nil { + if errors.Cause(err) == errors.ErrNotImplemented { + return input, httperrors.NewNotImplementedError("validating config of %s", input.Type) + } + return input, err + } + if !isValid { + return input, httperrors.NewInputParameterError(message) + } + if len(input.Name) == 0 { + input.Name = input.Type + } + return input, 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 { + return nil, err + } + q = q.NotEquals("type", api.WEBCONSOLE) + if len(input.Type) > 0 { + q.Filter(sqlchemy.Equals(q.Field("type"), input.Type)) + } + return q, nil +} + +func (cm *SConfigManager) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (api.ConfigDetails, error) { + return api.ConfigDetails{}, nil +} + +func (cm *SConfigManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.ConfigDetails { + sRows := cm.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + rows := make([]api.ConfigDetails, len(objs)) + for i := range rows { + rows[i].StandaloneResourceDetails = sRows[i] + } + return rows +} + +func (cm *SConfigManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + q, err := cm.SStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err != nil { + return q, nil + } + return q, nil +} + +func (cm *SConfigManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.ConfigListInput) (*sqlchemy.SQuery, error) { + q, err := cm.SStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StandaloneResourceListInput) + if err != nil { + return nil, err + } + return q, nil +} + +func (cm *SConfigManager) AllowPerformValidate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, cm, "validate") +} + +func (cm *SConfigManager) PerformValidate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ConfigValidateInput) (api.ConfigValidateOutput, error) { + var ( + output api.ConfigValidateOutput + err error + ) + if !utils.IsInStringArray(input.Type, []string{api.EMAIL, api.MOBILE, api.DINGTALK, api.FEISHU, api.WEBCONSOLE, api.WORKWX, api.FEISHU_ROBOT, api.DINGTALK_ROBOT, api.WORKWX_ROBOT}) { + return output, httperrors.NewInputParameterError("unkown type %q", input.Type) + } + if input.Content == nil { + return output, httperrors.NewMissingParameterError("content") + } + // validate + configs := make(map[string]string) + err = input.Content.Unmarshal(&configs) + if err != nil { + return output, err + } + isValid, message, err := NotifyService.ValidateConfig(ctx, input.Type, configs) + if err != nil { + if errors.Cause(err) == errors.ErrNotImplemented { + return output, httperrors.NewNotImplementedError("validating config of %s", input.Type) + } + return output, err + } + if !isValid { + output.IsValid = false + output.Message = message + } else { + output.IsValid = true + } + return output, nil +} + +func (self *SConfigManager) InitializeData() error { + ctx := context.Background() + userCred := auth.AdminCredential() + // fetch all configs + configs := make([]oldmodels.SConfig, 0, 5) + q := oldmodels.ConfigManager.Query() + err := db.FetchModelObjects(oldmodels.ConfigManager, q, &configs) + if err != nil { + return errors.Wrap(err, "db.FetchModelObjects") + } + + // build type==>config map + tcMap := make(map[string][]*oldmodels.SConfig) + for i := range configs { + t := configs[i].Type + if _, ok := tcMap[t]; !ok { + tcMap[t] = make([]*oldmodels.SConfig, 0, 3) + } + tcMap[t] = append(tcMap[t], &configs[i]) + } + + for t, configs := range tcMap { + cMap := make(map[string]string) + for _, config := range configs { + cMap[config.KeyText] = config.ValueText + } + newConfig := SConfig{ + Type: t, + Content: jsonutils.Marshal(cMap), + } + err := self.TableSpec().Insert(ctx, &newConfig) + if err != nil { + return errors.Wrap(err, "TableSpec().Insert") + } + for _, config := range configs { + err := config.Delete(ctx, userCred) + if err != nil { + return errors.Wrap(err, "Delete") + } + } + } + + // init webconsole's config + q = self.Query().Equals("type", api.WEBCONSOLE) + wsConfigs := make([]SConfig, 0, 2) + err = db.FetchModelObjects(self, q, &wsConfigs) + if err != nil && errors.Cause(err) != sql.ErrNoRows { + return errors.Wrap(err, "db.FetchModelObjects") + } + if len(wsConfigs) > 1 { + for i := 1; i < len(wsConfigs); i++ { + err := wsConfigs[i].Delete(ctx, userCred) + if err != nil { + return errors.Wrap(err, "Delete redundant") + } + } + } + + var config *SConfig + if len(wsConfigs) > 0 { + config = &wsConfigs[0] + } else { + config = &SConfig{ + Type: api.WEBCONSOLE, + } + } + config.Content = jsonutils.Marshal(map[string]string{ + "auth_uri": options.Options.AuthURL, + "admin_user": options.Options.AdminUser, + "admin_password": options.Options.AdminPassword, + "admin_tenant_name": options.Options.AdminProject, + }) + err = self.TableSpec().InsertOrUpdate(context.TODO(), config) + return nil +} + +// Fetch all SConfig struct which type is contactType. +func (self *SConfigManager) GetConfigByType(contactType string) (*SConfig, error) { + var config SConfig + q := self.Query() + q.Filter(sqlchemy.Equals(q.Field("type"), contactType)) + err := q.First(&config) + if err != nil { + return nil, errors.Wrap(err, "fail to fetch SConfigs by type") + } + return &config, nil +} + +func (self *SConfigManager) GetConfig(contactType string) (notifyv2.SConfig, error) { + config, err := self.GetConfigByType(contactType) + if err != nil { + return nil, err + } + ret := make(map[string]string) + err = config.Content.Unmarshal(&ret) + if err != nil { + return nil, errors.Wrap(err, "fail unmarshal config content") + } + return ret, nil +} + +func (self *SConfigManager) SetConfig(contactType string, config notifyv2.SConfig) error { + content := jsonutils.Marshal(config) + sConfig := &SConfig{ + Type: contactType, + Content: content, + } + return self.TableSpec().InsertOrUpdate(context.Background(), sConfig) +} diff --git a/pkg/notify/models/consts.go b/pkg/notify/models/consts.go deleted file mode 100644 index 39cbe63ab6..0000000000 --- a/pkg/notify/models/consts.go +++ /dev/null @@ -1,47 +0,0 @@ -// 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 models - -const ( - EMAIL = "email" - MOBILE = "mobile" - DINGTALK = "dingtalk" - FEISHU = "feishu" - WEBCONSOLE = "webconsole" - ROBOT = "robot" - - NOTIFY_RECEIVED = "received" // Received a task about sending a notification - NOTIFY_SENT = "sent" // Nofity module has sent notification, but result unkown - NOTIFY_OK = "sent_ok" // Notification was sent successfully - NOTIFY_FAIL = "sent_fail" // That sent a notification is failed - - CONTACT_INIT = "init" // Contact's status is init which means no verifying - CONTACT_VERIFYING = "verifying" // Contact's status is verifying - CONTACT_VERIFIED = "verified" // Contact's status is verified - - VERIFICATION_SENT = "sent" // Verification was sent - VERIFICATION_SENT_FAIL = "sent_fail" // Verification was sent failed - VERIFICATION_VERIFIED = "verified" // Verification was verified - VERIFICATION_TOKEN_EXPIRED = "Verification code expired" - VERIFICATION_TOKEN_INVALID = "Incorrect verification code" -) - -// Dingtalk account will be update automatically as mobile number change so that update dingtalk is not allowed -// In webconsole, uid is the same as contact. -var UpdateNotAllow = map[string]struct{}{ - DINGTALK: {}, - FEISHU: {}, - WEBCONSOLE: {}, -} diff --git a/pkg/notify/models/initdb.go b/pkg/notify/models/initdb.go index f82e4a4760..0a9d0901ac 100644 --- a/pkg/notify/models/initdb.go +++ b/pkg/notify/models/initdb.go @@ -27,8 +27,7 @@ func InitDB() error { * initialization order matters, do not change the order */ - ContactManager, - VerifyManager, + ReceiverManager, NotificationManager, ConfigManager, TemplateManager, diff --git a/pkg/notify/models/mod_config.go b/pkg/notify/models/mod_config.go deleted file mode 100644 index 521284ff84..0000000000 --- a/pkg/notify/models/mod_config.go +++ /dev/null @@ -1,252 +0,0 @@ -// 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 models - -import ( - "context" - "fmt" - "strconv" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/errors" - "yunion.io/x/sqlchemy" - - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/mcclient" - _interface "yunion.io/x/onecloud/pkg/notify/interface" - "yunion.io/x/onecloud/pkg/notify/options" -) - -type SConfigManager struct { - SStatusStandaloneResourceBaseManager -} - -var ConfigManager *SConfigManager - -func init() { - ConfigManager = &SConfigManager{ - SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager( - SConfig{}, - "notify_t_config", - "config", - "configs", - ), - } - ConfigManager.SetVirtualObject(ConfigManager) -} - -// SConfig is a table which storage (k,v) and its type. -// The three important concepts are key, value and type. -// Key and type uniquely identify a value. -type SConfig struct { - SStatusStandaloneResourceBase - - Type string `width:"15" nullable:"false" create:"required" list:"user"` - KeyText string `width:"30" nullable:"false" create:"required" list:"user"` - ValueText string `width:"256" nullable:"false" create:"required" list:"user"` -} - -// ListItemFilter is a hook function belong to IModelManager interface when Listing. -// This will Called in yunion.io/x/onecloud/pkg/cloudcommon/db.List function. -func (self *SConfigManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { - if !query.Contains("type") { - return q, nil - } - contactType, _ := query.GetString("type") - q.Filter(sqlchemy.Equals(q.Field("type"), contactType)) - return q, nil -} - -// GetValue fetch the SConfig struct corresponding to key and type. -func (self *SConfigManager) GetValue(key, contactType string) (*SConfig, error) { - q := self.Query() - q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("type"), contactType), sqlchemy.Equals(q.Field("key_text"), key))) - configs := make([]SConfig, 0, 1) - err := db.FetchModelObjects(self, q, &configs) - if err != nil { - return nil, errors.Wrap(err, "Fetch SConfig by key and type failed") - } - if len(configs) == 0 { - return nil, errors.Error("There is no SConfig whose key and type meet the requirement") - } - return &configs[0], nil -} - -func (self *SConfigManager) InitializeData() error { - q := self.Query() - q = q.Filter(sqlchemy.OR(sqlchemy.IsNotNull(q.Field("updated_at")), sqlchemy.IsNotNull(q.Field("created_at")), - sqlchemy.IsNotNull(q.Field("deleted_at")), sqlchemy.IsTrue(q.Field("deleted")), - sqlchemy.IsNotNull(q.Field("update_by")), sqlchemy.IsNotNull(q.Field("delete_by")))) - n, err := q.CountWithError() - if err != nil { - return err - } - if n <= 0 { - log.Debugf("need to init data for %s", self.TableSpec().Name()) - // need to update - sql := fmt.Sprintf("update %s set updated_at=gmt_modified, deleted=is_deleted, created_at=gmt_create, deleted_at=gmt_deleted, update_by=modified_by, delete_by=deleted_by", self.TableSpec().Name()) - q := sqlchemy.NewRawQuery(sql, "") - q.Row() - sql = fmt.Sprintf("update %s set type='mobile' where type='sms_aliyun'", self.TableSpec().Name()) - q = sqlchemy.NewRawQuery(sql, "") - q.Row() - } - - // init webconsole's config - sql := fmt.Sprintf("update %s set deleted='1' where type='webconsole'", self.TableSpec().Name()) - q = sqlchemy.NewRawQuery(sql) - q.Row() - configs := []SConfig{ - { - Type: "webconsole", - KeyText: "auth_uri", - ValueText: options.Options.AuthURL, - }, - { - Type: "webconsole", - KeyText: "admin_user", - ValueText: options.Options.AdminUser, - }, - { - Type: "webconsole", - KeyText: "admin_password", - ValueText: options.Options.AdminPassword, - }, - { - Type: "webconsole", - KeyText: "admin_tenant_name", - ValueText: options.Options.AdminProject, - }, - } - for _, config := range configs { - err := self.TableSpec().Insert(context.TODO(), &config) - if err != nil { - return err - } - } - return nil -} - -// Fetch all SConfig struct which type is contactType. -func (self *SConfigManager) GetConfigByType(contactType string) ([]SConfig, error) { - q := self.Query() - q.Filter(sqlchemy.Equals(q.Field("type"), contactType)) - configs := make([]SConfig, 0, 5) - err := db.FetchModelObjects(self, q, &configs) - if err != nil { - return nil, errors.Wrap(err, "Fetch SConfigs by type failed") - } - //if len(configs) == 0 { - // return nil, errors.Error("There is no SConfig whose type meet the requirement") - //} - return configs, nil -} - -func (self *SConfigManager) GetConfig(contactType string) (_interface.SConfig, error) { - configs, err := self.GetConfigByType(contactType) - if err != nil { - return nil, err - } - ret := make(map[string]string) - for i := range configs { - ret[configs[i].KeyText] = configs[i].ValueText - } - return ret, nil -} - -func (self *SConfigManager) SetConfig(contactType string, config _interface.SConfig) error { - return fmt.Errorf("SetConfig Not Implemented") -} - -type SConvertFunc func(*jsonutils.JSONDict) *jsonutils.JSONDict - -var ( - // toDisplay store SConvertFunc which convert the data from client to the form required by database of contactType - toDisplay map[string]SConvertFunc - // fromDisplay store SConvertFunc which convert the data from database to the form required by client of contactType - fromDisplay map[string]SConvertFunc -) - -func init() { - toDisplay = map[string]SConvertFunc{ - EMAIL: emailToDisplay, - } - fromDisplay = map[string]SConvertFunc{ - EMAIL: emailFromDisplay, - } -} - -func emailFromDisplay(dict *jsonutils.JSONDict) *jsonutils.JSONDict { - keys := dict.SortedKeys() - newKey := "" - for _, key := range keys { - switch key { - case "username", "password": - newKey = "mail." + key - case "hostname", "hostport": - newKey = "mail.smtp." + key - case "ssl_global": - newKey = "mail.global.ssl" - } - v, _ := dict.Get(key) - dict.Add(v, newKey) - dict.Remove(key) - } - return dict -} - -func emailToDisplay(dict *jsonutils.JSONDict) *jsonutils.JSONDict { - keys := dict.SortedKeys() - for _, key := range keys { - newKey := "" - value, _ := dict.Get(key) - switch key { - case "mail.username", "mail.password": - newKey = key[5:] - case "mail.smtp.hostname": - newKey = key[10:] - case "mail.smtp.hostport": - newKey = key[10:] - portStr, _ := value.GetString() - port, _ := strconv.Atoi(portStr) - value = jsonutils.NewInt(int64(port)) - case "mail.global.ssl": - newKey = "ssl_global" - sslStr, _ := value.GetString() - ssl, _ := strconv.ParseBool(sslStr) - value = jsonutils.NewBool(ssl) - } - dict.Add(value, newKey) - dict.Remove(key) - } - return dict -} - -func (self *SConfigManager) Display2Database(cType string, dict *jsonutils.JSONDict) *jsonutils.JSONDict { - cf, ok := fromDisplay[cType] - if ok { - return cf(dict) - } - return dict -} - -func (self *SConfigManager) Database2Display(cType string, dict *jsonutils.JSONDict) *jsonutils.JSONDict { - cf, ok := toDisplay[cType] - if ok { - return cf(dict) - } - return dict -} diff --git a/pkg/notify/models/mod_contact.go b/pkg/notify/models/mod_contact.go deleted file mode 100644 index 2eeee505dd..0000000000 --- a/pkg/notify/models/mod_contact.go +++ /dev/null @@ -1,404 +0,0 @@ -// 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 models - -import ( - "context" - "database/sql" - "fmt" - "strings" - "time" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/errors" - "yunion.io/x/pkg/util/sets" - "yunion.io/x/sqlchemy" - - api "yunion.io/x/onecloud/pkg/apis/notify" - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/auth" - "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/notify/utils" - "yunion.io/x/onecloud/pkg/util/rbacutils" - "yunion.io/x/onecloud/pkg/util/stringutils2" -) - -type SContactManager struct { - SStatusStandaloneResourceBaseManager -} - -var ContactManager *SContactManager - -func init() { - ContactManager = &SContactManager{ - SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager( - SContact{}, - "notify_t_contacts", - "contact", - "contacts", - ), - } - ContactManager.SetVirtualObject(ContactManager) -} - -type SContact struct { - SStatusStandaloneResourceBase - - UID string `width:"128" nullable:"false" create:"required" update:"user" list:"user" get:"user"` - ContactType string `width:"16" nullable:"false" create:"required" update:"user"` - Contact string `width:"64" nullable:"false" create:"required" update:"user"` - Enabled string `width:"5" nullable:"false" default:"1" create:"optional" update:"user"` - VerifiedAt time.Time `update:"user"` -} - -func (self *SContactManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return true -} - -func (self *SContactManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return true -} - -func (self *SContactManager) ResourceScope() rbacutils.TRbacScope { - return rbacutils.ScopeUser -} - -func (self *SContactManager) NamespaceScope() rbacutils.TRbacScope { - return rbacutils.ScopeUser -} - -func (self *SContactManager) FetchOwnerId(ctx context.Context, - data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) { - - return db.FetchUserInfo(ctx, data) -} - -func (self *SContactManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, - scope rbacutils.TRbacScope) *sqlchemy.SQuery { - if owner != nil { - if scope == rbacutils.ScopeUser { - if len(owner.GetUserId()) > 0 { - q = q.Equals("uid", owner.GetUserId()) - } - } - } - return q -} - -func (self *SContactManager) InitializeData() error { - q := self.Query() - q = q.Filter(sqlchemy.OR(sqlchemy.IsNotNull(q.Field("updated_at")), sqlchemy.IsTrue(q.Field("deleted")))) - n, err := q.CountWithError() - if err != nil { - return err - } - if n > 0 { - log.Debugf("no need to init data for %s", self.TableSpec().Name()) - // no need to init data - return nil - } - log.Debugf("need to init data for %s", self.TableSpec().Name()) - sql := fmt.Sprintf("update %s set updated_at=update_at, deleted=is_deleted", self.TableSpec().Name()) - q = sqlchemy.NewRawQuery(sql, "") - q.Row() - return nil -} - -// FetchByUIDs fetch all SContancts whose uid included in uids. -// If some elements of uids are uname of users, setting param 'uname' as true will fetch correct results. -func (self *SContactManager) FetchByUIDs(ctx context.Context, uids []string, uname bool) ([]SContact, error) { - var err error - if uname { - uids, err = self._UIDsFromUIDOrName(ctx, uids) - if err != nil { - return nil, err - } - } - q := self.Query() - q = q.Filter(sqlchemy.In(q.Field("uid"), uids)) - records := make([]SContact, 0, len(uids)) - err = db.FetchModelObjects(self, q, &records) - if err != nil { - return nil, err - } - return records, nil -} - -func (self *SContactManager) _UIDsFromUIDOrName(ctx context.Context, uidStrs []string) ([]string, error) { - users, err := utils.GetUsersWithoutRemote(ctx, uidStrs) - if err != nil { - return nil, err - } - uids := make([]string, 0, len(uidStrs)) - uidSet := sets.NewString(uidStrs...) - var ( - uid string - uname string - ) - for i := range users { - uid = users[i].Id - uname = users[i].Name - if uidSet.Has(uid) { - uids = append(uids, uid) - uidSet.Delete(uid) - continue - } - if uidSet.Has(uname) { - uids = append(uids, uid) - uidSet.Delete(uname) - continue - } - } - for _, uid = range uidSet.UnsortedList() { - uids = append(uids, uid) - } - log.Debugf("uids %s => %s", uidStrs, uids) - return uids, nil -} - -func (self *SContactManager) FetchByUIDAndCType(uid string, contactTypes []string) ([]SContact, error) { - q := self.Query("id", "uid", "contact_type", "contact", "enabled").Equals("uid", uid).In("contact_type", contactTypes) - records := make([]SContact, 0, len(contactTypes)) - err := db.FetchModelObjects(self, q, &records) - if err != nil { - return nil, err - } - return records, nil -} - -func (self *SContactManager) FetchByMore(uid, contact, contactType string) ([]SContact, error) { - q := self.Query().Equals("uid", uid).Equals("contact", contact).Equals("contact_type", contactType) - records := make([]SContact, 0, 1) - err := db.FetchModelObjects(self, q, &records) - if err != nil { - return nil, err - } - return records, nil -} - -func (self *SContact) getMoreDetail(ctx context.Context, userCred mcclient.TokenCredential, - out api.ContactDetails) (api.ContactDetails, error) { - - uname, err := utils.GetUsernameByID(ctx, self.UID) - if errors.Cause(err) == sql.ErrNoRows { - uname = self.UID - err = nil - } - if err != nil { - return out, err - } - - q := ContactManager.Query().Equals("uid", self.UID) - contacts := make([]SContact, 0) - err = db.FetchModelObjects(ContactManager, q, &contacts) - if err != nil { - return out, errors.Wrapf(err, "fetch Contacts of uid %s error", self.UID) - } - out.UID = self.UID - out.Name = uname - out.Details = jsonutils.Marshal(contacts).String() - - return out, nil -} - -func (manager *SContactManager) FetchCustomizeColumns( - ctx context.Context, - userCred mcclient.TokenCredential, - query jsonutils.JSONObject, - objs []interface{}, - fields stringutils2.SSortedStrings, - isList bool, -) []api.ContactDetails { - rows := make([]api.ContactDetails, len(objs)) - - stdRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) - - var err error - for i := range rows { - rows[i] = api.ContactDetails{ - ResourceBaseDetails: stdRows[i], - } - rows[i], err = objs[i].(*SContact).getMoreDetail(ctx, userCred, rows[i]) - if err != nil { - log.Errorf(err.Error()) - } - } - return rows -} - -func (self *SContact) GetExtraDetails( - ctx context.Context, - userCred mcclient.TokenCredential, - query jsonutils.JSONObject, - isList bool, -) (api.ContactDetails, error) { - return api.ContactDetails{}, nil -} - -// 联系方式列表 -func (self *SContactManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { - queryDict := query.(*jsonutils.JSONDict) - if queryDict.Contains("uid") { - uid, _ := queryDict.GetString("uid") - q = q.Equals("uid", uid) - } - // for now - if queryDict.Contains("filter") { - filterCon, _ := queryDict.GetString("filter") - queryDict.Remove("filter") - contain := "name.contains(" - index := strings.Index(filterCon, contain) - if index < 0 { - return q, nil - } - filterCon = filterCon[index+len(contain):] - index = strings.Index(filterCon, ")") - if index < 0 { - return q, nil - } - name := filterCon[:index] - ids, err := utils.GetUserIdsLikeName(ctx, name) - if err != nil { - return q, nil - } - q = q.In("uid", ids) - } - - scopeStr, err := query.GetString("scope") - if err != nil { - scopeStr = "project" - } - scope := rbacutils.TRbacScope(scopeStr) - - switch { - case scope.HigherEqual(rbacutils.ScopeSystem): - case scope.HigherEqual(rbacutils.ScopeDomain): - uids, err := self.uidsInDomain(ctx, userCred) - if err != nil { - return q, err - } - q = q.In("uid", uids) - default: - q = q.Equals("uid", userCred.GetUserId()) - } - - q = q.GroupBy("uid").Desc("created_at") - - return q, nil -} - -func (self *SContactManager) uidsInDomain(ctx context.Context, userCred mcclient.TokenCredential) ([]string, error) { - session := auth.GetSession(ctx, userCred, "", "") - params := jsonutils.NewDict() - params.Set("scope", jsonutils.NewString("domain")) - ret, err := modules.UsersV3.List(session, params) - if err != nil { - return nil, errors.Wrap(err, "modules.Userv3.List") - } - uids := make([]string, 0, len(ret.Data)) - for i := range ret.Data { - id, _ := ret.Data[i].GetString("id") - uids = append(uids, id) - } - return uids, nil -} - -// Contacts query all contacts by uids and contactType -func (self *SContactManager) Contacts(uids []string, contactType string) ([]SContact, error) { - contacts := make([]SContact, 0, len(uids)) - if contactType == WEBCONSOLE { - for _, uid := range uids { - contacts = append(contacts, SContact{ - UID: uid, - ContactType: WEBCONSOLE, - Contact: uid, - }) - } - return contacts, nil - } - - queryCon := contactType - if strings.Contains(contactType, ROBOT) { - queryCon = MOBILE - } - q := self.Query().Equals("contact_type", queryCon).Equals("enabled", "1").In("uid", uids) - err := db.FetchModelObjects(self, q, &contacts) - if err != nil { - return nil, err - } - - // For Robot Sender, only one message of the same content is sent for multiple users, - // so the user's contact information is a collection of all contact information - if strings.Contains(contactType, ROBOT) { - // hack - contactVals := make([]string, len(contacts)) - uidVals := make([]string, len(contacts)) - for i := range contacts { - contactVals[i] = contacts[i].Contact - uidVals[i] = contacts[i].UID - } - contacts = []SContact{ - { - UID: strings.Join(uidVals, ","), - ContactType: contactType, - Contact: strings.Join(contactVals, ","), - }, - } - } - return contacts, nil -} - -func (self *SContactManager) GetAllNotify(ctx context.Context, ids []string, contactType string, group bool) ([]SContact, error) { - var uids []string - var err error - - if !group { - if v := ctx.Value("uname"); v != nil { - ids, err = self._UIDsFromUIDOrName(ctx, ids) - if err != nil { - return nil, errors.Wrap(err, "fail to transfer array of UID or Uname to UIDs") - } - } - uids = ids - } else { - uid := make([]string, 0) - for _, id := range uids { - tmpUids, err := utils.GetUsersByGroupID(ctx, id) - if err != nil { - return nil, err - } - uid = append(uid, tmpUids...) - } - uids = uid - } - return self.Contacts(uids, contactType) -} - -type SContactResponse struct { - Id string - Name string - Details string -} - -func NewSContactResponse(ctx context.Context, uid string, details string) SContactResponse { - name, _ := utils.GetUsernameByID(ctx, uid) - return SContactResponse{ - Id: uid, - Name: name, - Details: details, - } -} diff --git a/pkg/notify/models/mod_notification.go b/pkg/notify/models/mod_notification.go deleted file mode 100644 index 4e6fcbc857..0000000000 --- a/pkg/notify/models/mod_notification.go +++ /dev/null @@ -1,506 +0,0 @@ -// 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 models - -import ( - "bytes" - "context" - "fmt" - "strings" - "time" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/errors" - "yunion.io/x/sqlchemy" - - api "yunion.io/x/onecloud/pkg/apis/notify" - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/cloudcommon/policy" - "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/notify/cache" - _interface "yunion.io/x/onecloud/pkg/notify/interface" - "yunion.io/x/onecloud/pkg/notify/options" - "yunion.io/x/onecloud/pkg/notify/utils" - "yunion.io/x/onecloud/pkg/util/rbacutils" - "yunion.io/x/onecloud/pkg/util/stringutils2" -) - -type SNotificationManager struct { - SStatusStandaloneResourceBaseManager -} - -var NotificationManager *SNotificationManager -var NotifyService _interface.INotifyService - -func init() { - NotificationManager = &SNotificationManager{ - SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager( - SNotification{}, - "notify_t_notification", - "notification", - "notifications", - ), - } - NotificationManager.SetVirtualObject(NotificationManager) -} - -func (self *SNotificationManager) ResourceScope() rbacutils.TRbacScope { - return rbacutils.ScopeUser -} - -func (self *SNotificationManager) NamespaceScope() rbacutils.TRbacScope { - return rbacutils.ScopeUser -} - -func (self *SNotificationManager) FetchOwnerId(ctx context.Context, - data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) { - - return db.FetchUserInfo(ctx, data) -} - -func (self *SNotificationManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, - scope rbacutils.TRbacScope) *sqlchemy.SQuery { - if owner != nil { - if scope == rbacutils.ScopeUser { - if len(owner.GetUserId()) > 0 { - q = q.Equals("uid", owner.GetUserId()) - } - } - } - return q -} - -type SNotification struct { - SStatusStandaloneResourceBase - - UID string `width:"128" nullable:"false" create:"required"` - ContactType string `width:"16" nullable:"false" create:"required" list:"user" index:"true"` - Topic string `width:"128" nullable:"true" create:"optional" list:"user"` - Priority string `width:"16" nullable:"true" create:"optional" list:"user"` - Msg string `create:"required"` - ReceivedAt time.Time `nullable:"true" list:"user" create:"optional"` - SendAt time.Time `nullable:"false"` - SendBy string `width:"128" nullable:"false"` - // ClusterID identify message with same topic, msg, priority - ClusterID string `width:"128" charset:"ascii" primary:"true" create:"optional" list:"user" get:"user"` -} - -type UserDetail struct { - Status string - Name string - ReceivedAt time.Time -} - -func (manager *SNotificationManager) FetchCustomizeColumns( - ctx context.Context, - userCred mcclient.TokenCredential, - query jsonutils.JSONObject, - objs []interface{}, - fields stringutils2.SSortedStrings, - isList bool, -) []api.NotificationDetails { - rows := make([]api.NotificationDetails, len(objs)) - - resRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) - - for i := range rows { - rows[i] = api.NotificationDetails{ - ResourceBaseDetails: resRows[i], - } - rows[i], _ = objs[i].(*SNotification).getMoreDetails(ctx, query, rows[i]) - } - - return rows -} - -func (self *SNotification) GetExtraDetails( - ctx context.Context, - userCred mcclient.TokenCredential, - query jsonutils.JSONObject, - isList bool, -) (api.NotificationDetails, error) { - return api.NotificationDetails{}, nil -} - -func (self *SNotification) getMoreDetails(ctx context.Context, query jsonutils.JSONObject, out api.NotificationDetails) (api.NotificationDetails, error) { - var err error - - var scopeStr string - scopeStr, err = query.GetString("scope") - if err != nil { - scopeStr = "system" - } - scope := rbacutils.TRbacScope(scopeStr) - - var userDetails []UserDetail - if scope.HigherEqual(rbacutils.ScopeSystem) { - userDetails, err = NotificationManager.fetchUserDetailByClusterID(ctx, self.ClusterID) - if err != nil { - return out, errors.Wrap(err, "NotificationManager.fetchUserDetailByClusterID") - } - } else { - userDetail := UserDetail{ - Status: self.Status, - Name: self.UID, - ReceivedAt: self.ReceivedAt, - } - name, err := utils.GetUsernameByID(ctx, self.UID) - if err == nil && len(name) != 0 { - userDetail.Name = name - } - userDetails = []UserDetail{userDetail} - } - - out.UserList = jsonutils.Marshal(userDetails) - return out, nil -} - -func (self *SNotificationManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return db.IsAdminAllowList(userCred, self) -} - -func (self *SNotificationManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return db.IsAdminAllowCreate(userCred, self) -} - -type sUpdate struct { - ID string - UID string - Topic string - Priority string - ContactType string -} - -func (self *SNotificationManager) InitializeData() error { - scope := time.Duration(options.Options.InitNotificationScope) * time.Hour - time := time.Now().Add(-scope) - q := self.Query("id", "uid", "topic", "priority", "contact_type").GE("created_at", - time).Desc("received_at").Equals("contact_type", "webconsole") - q = q.Filter(sqlchemy.OR(sqlchemy.IsNull(q.Field("cluster_id")), sqlchemy.IsEmpty(q.Field("cluster_id")))) - rows, err := q.Rows() - if err != nil { - return err - } - updates, update := make([]sUpdate, 0, 10), sUpdate{} - for rows.Next() { - err := rows.Scan(&update.ID, &update.UID, &update.Topic, &update.Priority, &update.ContactType) - if err == nil { - updates = append(updates, update) - } - } - log.Debugf("this is total %d updates", len(updates)) - - // updates is too little - //if len(updates) < 100 { - // updates = updates[:0] - // q := self.Query("id", "uid", "topic", "priority", "contact_type").Desc("received_at").Equals("contact_type", - // "webconsole").Limit(500) - // q = q.Filter(sqlchemy.OR(sqlchemy.IsNull(q.Field("cluster_id")), sqlchemy.IsEmpty(q.Field("cluster_id")))) - // rows, err := q.Rows() - // if err != nil { - // return err - // } - // for rows.Next() { - // err := rows.Scan(&update.ID, &update.UID, &update.Topic, &update.Priority, &update.ContactType) - // if err == nil { - // updates = append(updates, update) - // } - // } - // log.Debugf("this is total %d updates", len(updates)) - //} - - cache := make([]string, 0, 10) - if len(updates) > 0 { - cache = append(cache, updates[0].ID) - } - for i := 1; i < len(updates); i++ { - if updates[i].Topic == updates[i-1].Topic && updates[i].Priority == updates[i-1].Priority { - - cache = append(cache, updates[i].ID) - continue - } - err = self.syncDatabase(cache) - if err != nil { - return errors.Wrap(err, "exec sql error") - } - cache = cache[:0] - if i < len(updates)-1 { - cache = append(cache, updates[i].ID) - } - } - if len(cache) == 0 { - return nil - } - err = self.syncDatabase(cache) - if err != nil { - return errors.Wrap(err, "exec sql error") - } - - return nil -} - -func (self *SNotificationManager) syncDatabase(ids []string) error { - - sql := "update %s set updated_at=update_at, deleted=is_deleted, cluster_id='%s' where id in %s" - - newUid := DefaultUUIDGenerator() - - buffer := new(strings.Builder) - buffer.WriteString("(") - for _, id := range ids { - buffer.WriteString("'") - buffer.WriteString(id) - buffer.WriteString("', ") - } - newSql := fmt.Sprintf(sql, self.TableSpec().Name(), newUid, buffer.String()[:buffer.Len()-2]+")") - q := sqlchemy.NewRawQuery(newSql) - rows, err := q.Rows() - defer rows.Close() - return err -} - -// 通知消息列表 -func (self *SNotificationManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, - input api.NotificationListInput) (*sqlchemy.SQuery, error) { - - // no domainID for now - scopeStr := "system" - if len(input.Scope) > 0 { - scopeStr = input.Scope - } - scope := rbacutils.TRbacScope(scopeStr) - - if !scope.HigherEqual(rbacutils.ScopeSystem) { - q = q.Equals("uid", userCred.GetUserId()) - } - - if len(input.ContactType) > 0 { - q = q.Equals("contact_type", input.ContactType) - } - - q = q.GroupBy("cluster_id").Desc("received_at") - return q, nil -} - -func (self *SNotificationManager) BatchCreate(ctx context.Context, data jsonutils.JSONObject, contacts []SContact) ([]string, error) { - userCred := policy.FetchUserCredential(ctx) - ownerID, err := utils.FetchOwnerId(ctx, NotificationManager, userCred, jsonutils.JSONNull) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - msg, _ := data.GetString("msg") - priority, _ := data.GetString("priority") - topic, _ := data.GetString("topic") - createFailed, createSuccess, contactSuccess := make([]string, 0), make([]*SNotification, 0, len(contacts)/2), make([]string, 0, len(contacts)/2) - - now, clusterId := time.Now(), DefaultUUIDGenerator() - for i := range contacts { - createData := map[string]interface{}{ - "uid": contacts[i].UID, - "contact_type": contacts[i].ContactType, - "topic": topic, - "priority": priority, - "msg": msg, - "received_at": now, - "send_by": userCred.GetUserId(), - "status": NOTIFY_RECEIVED, - "cluster_id": clusterId, - } - model, err := db.DoCreate(self, ctx, userCred, jsonutils.JSONNull, jsonutils.Marshal(createData), ownerID) - if err != nil { - createFailed = append(createFailed, contacts[i].ID) - } else { - createSuccess = append(createSuccess, model.(*SNotification)) - contactSuccess = append(contactSuccess, contacts[i].Contact) - } - } - Send(createSuccess, userCred, contactSuccess) - if len(createFailed) != 0 { - errInfo := new(bytes.Buffer) - errInfo.WriteString("notifications whose uid are ") - for i := range createFailed { - errInfo.WriteString(createFailed[i]) - errInfo.WriteString(", ") - } - errInfo.Truncate(errInfo.Len() - 2) - errInfo.WriteString("created failed.") - log.Errorf(errInfo.String()) - return nil, errors.Error("Not all notifications were sent successfully") - } - notificationIDs := make([]string, len(createSuccess)) - for i := range createSuccess { - notificationIDs[i] = createSuccess[i].ID - } - return notificationIDs, nil -} - -func (self *SNotificationManager) fetchUserDetailByClusterID(ctx context.Context, clusterID string) ([]UserDetail, - error) { - q := self.Query("uid", "status", "received_at").Equals("cluster_id", clusterID) - row, err := q.Rows() - if err != nil { - return nil, err - } - ret := make([]UserDetail, 0) - userIds := make([]string, 0) - var userId, status string - var receviedAt time.Time - for row.Next() { - err := row.Scan(&userId, &status, &receviedAt) - if err != nil { - return nil, errors.Wrap(err, "sql.row parse error") - } - userIds = append(userIds, userId) - ret = append(ret, UserDetail{ - Status: status, - Name: userId, - ReceivedAt: receviedAt, - }) - } - - userMap, err := cache.UserCacheManager.FetchUsersByIDs(ctx, userIds) - if err != nil { - return nil, errors.Wrap(err, "fetch users by ids failed") - } - for i := range ret { - if user, ok := userMap[ret[i].Name]; ok { - ret[i].Name = user.Name - } - } - - return ret, nil -} - -func (self *SNotificationManager) FetchFailed(lastTime time.Time) ([]SNotification, error) { - q := self.Query() - q.Filter(sqlchemy.AND(sqlchemy.GE(q.Field("created_at"), lastTime), sqlchemy.Equals(q.Field("status"), NOTIFY_FAIL))) - records := make([]SNotification, 0, 10) - err := db.FetchModelObjects(self, q, &records) - if err != nil { - return nil, err - } - return records, nil -} - -func (self *SNotification) SetStatus(userCred mcclient.TokenCredential, status string, reason string) error { - if self.Status == status { - return nil - } - oldStatus := self.Status - _, err := db.Update(self, func() error { - self.Status = status - return nil - }) - if err != nil { - return err - } - if userCred != nil { - notes := fmt.Sprintf("%s=>%s", oldStatus, status) - if len(reason) > 0 { - notes = fmt.Sprintf("%s: %s", notes, reason) - } - db.OpsLog.LogEvent(self, db.ACT_UPDATE_STATUS, notes, userCred) - } - return nil -} - -func (self *SNotification) SetSentAndTime(userCred mcclient.TokenCredential) error { - status := NOTIFY_SENT - if self.Status == status { - return nil - } - oldStatus := self.Status - _, err := db.Update(self, func() error { - self.Status = status - self.SendAt = time.Now() - return nil - }) - if err != nil { - return err - } - reason := "sent notification" - if userCred != nil { - notes := fmt.Sprintf("%s=>%s", oldStatus, status) - if len(reason) > 0 { - notes = fmt.Sprintf("%s: %s", notes, reason) - } - db.OpsLog.LogEvent(self, db.ACT_UPDATE_STATUS, notes, userCred) - } - return nil -} - -func (self *SNotification) SetStatusWithoutUserCred(status string) error { - _, err := db.Update(self, func() error { - self.Status = status - return nil - }) - if err != nil { - return err - } - return nil -} -func sendWithoutUserCred(notifications []SNotification) { - // limit the number of Concurrency - Max := 10 - limit := make(chan struct{}, Max) - sendone := func(notification SNotification) { - defer func() { - <-limit - }() - // Get contact - contact, err := ContactManager.FetchByUIDAndCType(notification.UID, []string{notification.ContactType}) - if err != nil { - log.Debugf("fail to fetch contacts with uid '%s' in ReSend Cron Job", notification.UID) - return - } - if len(contact) == 0 { - return - } - // sent_at update todo - notification.SetStatusWithoutUserCred(NOTIFY_SENT) - err = NotifyService.Send(context.Background(), notification.ContactType, contact[0].Contact, notification.Topic, - notification.Msg, - notification.Priority) - if err == nil { - return - } - if err != nil { - log.Errorf("Send notification failed in ReSend Cron Job: %s.", err.Error()) - notification.SetStatusWithoutUserCred(NOTIFY_FAIL) - } else { - notification.SetStatusWithoutUserCred(NOTIFY_OK) - } - } - for i := range notifications { - limit <- struct{}{} - go sendone(notifications[i]) - } - // wait all finish - for i := 0; i < Max; i++ { - limit <- struct{}{} - } -} - -func ReSend(seconds int) { - scope := time.Duration(seconds+30) * time.Second - notifications, err := NotificationManager.FetchFailed(time.Now().Add(-scope)) - if err != nil { - return - } - log.Debugf("Start to resend message with a total of %d", len(notifications)) - sendWithoutUserCred(notifications) -} diff --git a/pkg/notify/models/mod_verify.go b/pkg/notify/models/mod_verify.go deleted file mode 100644 index f9292dbdf6..0000000000 --- a/pkg/notify/models/mod_verify.go +++ /dev/null @@ -1,134 +0,0 @@ -// 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 models - -import ( - "context" - "fmt" - "time" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/sqlchemy" - - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/notify/utils" -) - -type SVerifyManager struct { - SStatusStandaloneResourceBaseManager -} - -var VerifyManager *SVerifyManager - -func init() { - VerifyManager = &SVerifyManager{ - SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager( - SVerify{}, - "notify_t_verify", - "verification", - "verifications", - ), - } - VerifyManager.SetVirtualObject(VerifyManager) -} - -type SVerify struct { - SStatusStandaloneResourceBase - - CID string `width:"128" nullable:"false" create:"required" list:"user"` - Token string `width:"200" nullable:"false" create:"required" list:"user"` - SendAt time.Time `nullable:"true" create:"optional"` - ExpireAt time.Time `create:"required" list:"user"` -} - -// NewSVerify Generate a SVerify instance which implement a Verification Token. -func NewSVerify(contactType string, cid string) *SVerify { - var token string - var expireAt time.Time - now := time.Now() - if contactType == EMAIL { - token = utils.GenerateEmailToken(32) - expireAt = now.Add(12 * time.Hour) - } else { - token = utils.GenerateMobileToken() - expireAt = now.Add(2 * time.Minute) - } - ret := &SVerify{ - CID: cid, - Token: token, - ExpireAt: expireAt, - SendAt: now, - } - ret.ID = DefaultUUIDGenerator() - ret.SetModelManager(VerifyManager, ret) - return ret -} - -func (self *SVerifyManager) InitializeData() error { - q := self.Query() - q = q.Filter(sqlchemy.OR(sqlchemy.IsNotNull(q.Field("updated_at")), sqlchemy.IsTrue(q.Field("deleted")))) - n, err := q.CountWithError() - if err != nil { - return err - } - if n > 0 { - log.Debugf("no need to init data for %s", self.TableSpec().Name()) - // no need to init data - return nil - } - log.Debugf("need to init data for %s", self.TableSpec().Name()) - sql := fmt.Sprintf("update %s set updated_at=update_at, deleted=is_deleted", self.TableSpec().Name()) - q = sqlchemy.NewRawQuery(sql, "") - q.Row() - return nil -} - -func (self *SVerifyManager) FetchByCID(cid string, filter func(q *sqlchemy.SQuery) *sqlchemy.SQuery) ([]SVerify, error) { - q := self.Query() - q.Filter(sqlchemy.Equals(q.Field("cid"), cid)) - q = filter(q) - records := make([]SVerify, 0, 1) - err := db.FetchModelObjects(self, q, &records) - if err != nil { - return nil, err - } - return records, nil -} - -func (self *SVerifyManager) FetchByID(id string) ([]SVerify, error) { - q := self.Query() - q.Filter(sqlchemy.Equals(q.Field("id"), id)) - records := make([]SVerify, 0, 1) - err := db.FetchModelObjects(self, q, &records) - if err != nil { - return nil, err - } - return records, nil -} - -func (self *SVerifyManager) Create(ctx context.Context, userCred mcclient.TokenCredential, verify *SVerify) error { - data := jsonutils.Marshal(verify) - ownerID, err := utils.FetchOwnerId(ctx, self, userCred, data) - if err != nil { - return err - } - _, err = db.DoCreate(self, ctx, userCred, jsonutils.JSONNull, data, ownerID) - if err != nil { - return err - } - return nil -} diff --git a/pkg/notify/models/notification.go b/pkg/notify/models/notification.go new file mode 100644 index 0000000000..2de8324252 --- /dev/null +++ b/pkg/notify/models/notification.go @@ -0,0 +1,419 @@ +// 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 models + +import ( + "context" + "database/sql" + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/sets" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/notify" + "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/mcclient" + notifyv2 "yunion.io/x/onecloud/pkg/notify" + "yunion.io/x/onecloud/pkg/notify/oldmodels" + "yunion.io/x/onecloud/pkg/notify/options" + "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SNotificationManager struct { + db.SStatusStandaloneResourceBaseManager +} + +var NotificationManager *SNotificationManager +var NotifyService notifyv2.INotifyService + +func init() { + NotificationManager = &SNotificationManager{ + SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager( + SNotification{}, + "notifications_tbl", + "notification", + "notifications", + ), + } + NotificationManager.SetVirtualObject(NotificationManager) +} + +type SNotification struct { + db.SStatusStandaloneResourceBase + + ContactType string `width:"16" nullable:"false" create:"required" list:"user" get:"user" index:"true"` + // swagger:ignore + Topic string `width:"128" nullable:"true" create:"required"` + Priority string `width:"16" nullable:"true" create:"optional" list:"user" get:"user"` + // swagger:ignore + Message string `create:"required"` + ReceivedAt time.Time `nullable:"true" list:"user" get:"user"` + SendTimes int +} + +func (nm *SNotificationManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.NotificationCreateInput) (api.NotificationCreateInput, error) { + // check uids, rids and contacts + if len(input.Receivers) == 0 && len(input.Contacts) == 0 { + return input, httperrors.NewMissingParameterError("receivers | contacts") + } + // check receivers + if len(input.Receivers) > 0 { + receivers, err := ReceiverManager.FetchByIdOrNames(ctx, input.Receivers...) + if err != nil { + return input, errors.Wrap(err, "ReceiverManager.FetchByIDs") + } + idSet := sets.NewString() + nameSet := sets.NewString() + for i := range receivers { + idSet.Insert(receivers[i].Id) + nameSet.Insert(receivers[i].Name) + } + for _, re := range input.Receivers { + if idSet.Has(re) || nameSet.Has(re) { + continue + } + return input, httperrors.NewInputParameterError("no such receiver whose uid is %q", re) + } + input.Receivers = idSet.UnsortedList() + } + nowStr := time.Now().Format("2006-01-02 15:04:05") + // hack + input.Name = fmt.Sprintf("%s(%s)", input.Topic, nowStr) + return input, nil +} + +func (n *SNotification) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + n.ReceivedAt = time.Now() + n.Id = db.DefaultUUIDGenerator() + var input api.NotificationCreateInput + err := data.Unmarshal(&input) + if err != nil { + return err + } + for i := range input.Receivers { + _, err := ReceiverNotificationManager.Create(ctx, userCred, input.Receivers[i], n.Id) + if err != nil { + return errors.Wrap(err, "ReceiverNotificationManager.Create") + } + } + return nil +} + +func (n *SNotification) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + n.SetStatus(userCred, api.NOTIFICATION_STATUS_RECEIVED, "") + task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "") + if err != nil { + log.Errorf("NotificationSendTask newTask error %v", err) + } else { + task.ScheduleRun(nil) + } +} + +func (nm *SNotificationManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.NotificationDetails { + rows := make([]api.NotificationDetails, len(objs)) + + resRows := nm.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + + var err error + for i := range rows { + rows[i], err = objs[i].(*SNotification).getMoreDetails(ctx, query, rows[i]) + if err != nil { + log.Errorf("Notification.getMoreDetails: %v", err) + } + rows[i].StatusStandaloneResourceDetails = resRows[i] + } + + return rows +} + +func (n *SNotification) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.NotificationDetails, error) { + return api.NotificationDetails{}, nil +} + +func (n *SNotification) ReceiverNotificationsNotOK() ([]SReceiverNotification, error) { + rnq := ReceiverNotificationManager.Query().Equals("notification_id", n.Id).NotEquals("status", api.RECEIVER_NOTIFICATION_OK) + rns := make([]SReceiverNotification, 0, 1) + err := db.FetchModelObjects(ReceiverNotificationManager, rnq, &rns) + if err != nil { + return nil, err + } + return rns, nil +} + +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"))) + ret := make([]api.ReceiveDetail, 0, 2) + err := q.All(&ret) + if err != nil && errors.Cause(err) != sql.ErrNoRows { + log.Errorf("SQuery.All: %v", err) + return nil, err + } + return ret, nil +} + +func (n *SNotification) getMoreDetails(ctx context.Context, query jsonutils.JSONObject, out api.NotificationDetails) (api.NotificationDetails, error) { + // get title adn content + p, err := TemplateManager.NotifyFilter(n.ContactType, n.Topic, n.Message) + if err != nil { + return out, err + } + out.Title = p.Title + out.Content = p.Message + // get receive details + out.ReceiveDetails, err = n.ReceiveDetails() + if err != nil { + return out, err + } + return out, nil +} + +func (nm *SNotificationManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + if !data.Contains("contacts") { + return db.IsAdminAllowCreate(userCred, nm) + } + return true +} + +func (nm *SNotificationManager) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +func (nm *SNotificationManager) ResourceScope() rbacutils.TRbacScope { + return rbacutils.ScopeUser +} + +func (nm *SNotificationManager) NamespaceScope() rbacutils.TRbacScope { + return rbacutils.ScopeSystem +} + +func (nm *SNotificationManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) { + return db.FetchUserInfo(ctx, data) +} + +func (nm *SNotificationManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { + if owner == nil { + return q + } + switch scope { + case rbacutils.ScopeDomain: + subRq := ReceiverManager.Query("id").Equals("domain_id", owner.GetDomainId()).SubQuery() + subRNq := ReceiverNotificationManager.Query("notification_id").Join(subRq, sqlchemy.Equals(q.Field("receiver_id"), subRq.Field("id"))).SubQuery() + q = q.Join(subRNq, sqlchemy.Equals(q.Field("id"), subRNq.Field("notification_id"))) + case rbacutils.ScopeProject, rbacutils.ScopeUser: + subq := ReceiverNotificationManager.Query("notification_id").Equals("receiver_id", owner.GetUserId()).SubQuery() + q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("notification_id"))) + } + return q +} + +func (n *SNotification) AddOne() error { + _, err := db.Update(n, func() error { + n.SendTimes += 1 + return nil + }) + return err +} + +const ( + NOTIFY_RECEIVED = "received" // Received a task about sending a notification + NOTIFY_SENT = "sent" // Nofity module has sent notification, but result unkown + NOTIFY_OK = "sent_ok" // Notification was sent successfully + NOTIFY_FAIL = "sent_fail" // That sent a notification is failed + NOTIFY_REMOVED = "removed" +) + +func (self *SNotificationManager) singleRowLineQuery(sqlStr string, dest ...interface{}) error { + q := sqlchemy.NewRawQuery(sqlStr) + rows, err := q.Rows() + if err != nil { + return errors.Wrap(err, "q.Rows") + } + defer rows.Close() + for rows.Next() { + err := rows.Scan(dest...) + if err != nil { + return errors.Wrap(err, "rows.Scan") + } + return nil + } + return sql.ErrNoRows +} + +func (self *SNotificationManager) InitializeData() error { + // check + sqlStr := fmt.Sprintf( + "select count(*) as total from (select cluster_id from %s where status='%s' and contact_type='webconsole' group by cluster_id) as cluster", + oldmodels.NotificationManager.TableSpec().Name(), + NOTIFY_REMOVED, + ) + var count int + err := self.singleRowLineQuery(sqlStr, &count) + if err != nil { + return err + } + if count >= options.Options.MaxSyncNotification { + return nil + } + + limitTimeStr := time.Now().Add(time.Duration(-30) * time.Hour * 24).Format("2006-01-02 15:04:05") + + // get min received_at + var minReceivedAt time.Time + sqlStr = fmt.Sprintf( + "select min(received_at) as min_received_at from (select received_at from %s where received_at > '%s' and contact_type = 'webconsole' group by cluster_id order by received_at desc limit %d) as cluster", + oldmodels.NotificationManager.TableSpec().Name(), + limitTimeStr, + options.Options.MaxSyncNotification, + ) + err = self.singleRowLineQuery(sqlStr, &minReceivedAt) + if err != nil { + return err + } + log.Infof("minReceivedAt: %s", minReceivedAt) + + ctx := context.Background() + q := oldmodels.NotificationManager.Query().Equals("contact_type", api.WEBCONSOLE).GT("received_at", minReceivedAt).NotEquals("status", NOTIFY_REMOVED) + n := q.Count() + log.Infof("total %d notifications to sync", n) + oldNotifications := make([]oldmodels.SNotification, 0, n) + err = db.FetchModelObjects(oldmodels.NotificationManager, q, &oldNotifications) + if err != nil { + return errors.Wrap(err, "db.FetchModelObjects") + } + + // build cluster=>Notification + cnMap := make(map[string][]*oldmodels.SNotification) + for i := range oldNotifications { + clusterId := oldNotifications[i].ClusterID + if _, ok := cnMap[clusterId]; !ok { + cnMap[clusterId] = make([]*oldmodels.SNotification, 0, 2) + } + cnMap[clusterId] = append(cnMap[clusterId], &oldNotifications[i]) + } + + for _, oldNotifications := range cnMap { + oldNotificaion := oldNotifications[0] + newNotification := SNotification{ + ContactType: oldNotificaion.ContactType, + Topic: oldNotificaion.Topic, + Priority: oldNotificaion.Priority, + Message: oldNotificaion.Msg, + ReceivedAt: oldNotificaion.ReceivedAt, + } + newNotification.Id = db.DefaultUUIDGenerator() + statusMap := make(map[string]int, 4) + for _, oldNotificaion := range oldNotifications { + rn := SReceiverNotification{ + ReceiverID: oldNotificaion.UID, + NotificationID: newNotification.Id, + SendAt: oldNotificaion.SendAt, + SendBy: oldNotificaion.SendBy, + Status: oldNotificaion.Status, + } + if rn.Status == NOTIFY_SENT { + rn.Status = api.NOTIFICATION_STATUS_SENDING + } + statusMap[rn.Status] += 1 + err := ReceiverNotificationManager.TableSpec().Insert(ctx, &rn) + if err != nil { + return errors.Wrap(err, "TableSpec().Insert") + } + } + switch { + case statusMap[api.RECEIVER_NOTIFICATION_OK] == len(oldNotifications): + newNotification.Status = api.NOTIFICATION_STATUS_OK + case statusMap[api.RECEIVER_NOTIFICATION_RECEIVED] == len(oldNotifications): + newNotification.Status = api.NOTIFICATION_STATUS_RECEIVED + case statusMap[api.RECEIVER_NOTIFICATION_FAIL] == len(oldNotifications): + newNotification.Status = api.NOTIFICATION_STATUS_FAILED + case statusMap[api.RECEIVER_NOTIFICATION_FAIL] == 0 && statusMap[api.RECEIVER_NOTIFICATION_SENT] > 0: + newNotification.Status = api.NOTIFICATION_STATUS_SENDING + default: + newNotification.Status = api.NOTIFICATION_STATUS_PART_OK + } + err := self.TableSpec().InsertOrUpdate(ctx, &newNotification) + if err != nil { + return errors.Wrap(err, "TableSpec().InsertOrUpdate") + } + + // mark removed + for _, oldNotificaion := range oldNotifications { + _, err := db.Update(oldNotificaion, func() error { + oldNotificaion.Status = NOTIFY_REMOVED + return nil + }) + if err != nil { + return errors.Wrap(err, "Delete") + } + } + } + return nil +} + +// 通知消息列表 +func (nm *SNotificationManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.NotificationListInput) (*sqlchemy.SQuery, error) { + q, err := nm.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput) + if err != nil { + return nil, err + } + if len(input.ContactType) > 0 { + q = q.Equals("contact_type", input.ContactType) + } + if len(input.ReceiverId) > 0 { + subq := ReceiverNotificationManager.Query("notification_id").Equals("receiver_id", input.ReceiverId).SubQuery() + q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("notification_id"))) + } + return q, nil +} + +func (nm *SNotificationManager) ReSend(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + q := nm.Query().NotEquals("status", api.NOTIFICATION_STATUS_OK).LT("send_times", options.Options.MaxSendTimes) + ns := make([]SNotification, 0, 2) + err := db.FetchModelObjects(nm, q, &ns) + if err != nil { + log.Errorf("fail to FetchModelObjects: %v", err) + return + } + for i := range ns { + task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", &ns[i], userCred, nil, "", "") + if err != nil { + log.Errorf("NotificationSendTask newTask error %v", err) + } else { + task.ScheduleRun(nil) + } + } +} diff --git a/pkg/notify/models/receiver.go b/pkg/notify/models/receiver.go new file mode 100644 index 0000000000..97d634af78 --- /dev/null +++ b/pkg/notify/models/receiver.go @@ -0,0 +1,884 @@ +// 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 models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/pkg/util/sets" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/apis" + api "yunion.io/x/onecloud/pkg/apis/notify" + "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/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/informer" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/notify/oldmodels" + "yunion.io/x/onecloud/pkg/util/httputils" + "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +var ( + AllContactTypes = []string{ + api.EMAIL, + api.MOBILE, + api.DINGTALK, + api.FEISHU, + api.WORKWX, + api.WEBCONSOLE, + } + AllSubContactTypes = []string{ + api.DINGTALK, + api.FEISHU, + api.WORKWX, + api.WEBCONSOLE, + } + AllRobotContactTypes = []string{ + api.FEISHU_ROBOT, + api.DINGTALK_ROBOT, + api.WORKWX_ROBOT, + } +) + +type SReceiverManager struct { + db.SStatusStandaloneResourceBaseManager + db.SDomainizedResourceBaseManager + db.SEnabledResourceBaseManager +} + +var ReceiverManager *SReceiverManager + +func init() { + ReceiverManager = &SReceiverManager{ + SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager( + SReceiver{}, + "receivers_tbl", + "receiver", + "receivers", + ), + } + ReceiverManager.SetVirtualObject(ReceiverManager) +} + +type SReceiver struct { + db.SStatusStandaloneResourceBase + db.SDomainizedResourceBase + db.SEnabledResourceBase + + Email string `width:"64" nullable:"false" create:"optional" update:"user" get:"user" list:"admin"` + Mobile string `width:"16" nullable:"false" create:"optional" update:"user" get:"user" list:"admin"` + + // swagger:ignore + EnabledEmail tristate.TriState `nullable:"false" default:"false" update:"user"` + // swagger:ignore + VerifiedEmail tristate.TriState `nullable:"false" default:"false" update:"user"` + + // swagger:ignore + EnabledMobile tristate.TriState `nullable:"false" default:"false" update:"user"` + // swagger:ignore + VerifiedMobile tristate.TriState `nullable:"false" default:"false" update:"user"` + + // swagger:ignore + subContactCache map[string]*SSubContact `json:"-"` +} + +func (rm *SReceiverManager) InitializeData() error { + ctx := context.Background() + userCred := auth.AdminCredential() + log.Infof("Init Receiver...") + // Fetch all old SContact + q := oldmodels.ContactManager.Query() + contacts := make([]oldmodels.SContact, 0, 50) + err := db.FetchModelObjects(oldmodels.ContactManager, q, &contacts) + if err != nil { + return errors.Wrap(err, "db.FetchModelObjects") + } + + // build uid map + uids := make([]string, 0, 10) + contactMap := make(map[string][]*oldmodels.SContact, 10) + for i := range contacts { + uid := contacts[i].UID + if _, ok := contactMap[uid]; !ok { + contactMap[uid] = make([]*oldmodels.SContact, 0, 4) + uids = append(uids, uid) + } + contactMap[uid] = append(contactMap[uid], &contacts[i]) + } + + // build uid->uname map + userMap, err := oldmodels.UserCacheManager.FetchUsersByIDs(context.Background(), uids) + if err != nil { + return errors.Wrap(err, "oldmodels.UserCacheManager.FetchUsersByIDs") + } + + // build Receivers + for uid, contacts := range contactMap { + var receiver SReceiver + receiver.subContactCache = make(map[string]*SSubContact) + receiver.Enabled = tristate.True + receiver.Status = api.RECEIVER_STATUS_READY + receiver.Id = uid + user, ok := userMap[uid] + if !ok { + log.Errorf("no user %q in usercache", uid) + } else { + receiver.Name = user.Name + receiver.DomainId = user.DomainId + } + webconsole := false + for _, contact := range contacts { + switch contact.ContactType { + case api.EMAIL: + receiver.Email = contact.Contact + if contact.Enabled == "1" { + receiver.EnabledEmail = tristate.True + } else { + receiver.EnabledEmail = tristate.False + } + if contact.Status == oldmodels.CONTACT_VERIFIED { + receiver.VerifiedEmail = tristate.True + } else { + receiver.VerifiedEmail = tristate.False + } + case api.MOBILE: + receiver.Mobile = contact.Contact + if contact.Enabled == "1" { + receiver.EnabledMobile = tristate.True + } else { + receiver.EnabledMobile = tristate.False + } + if contact.Status == oldmodels.CONTACT_VERIFIED { + receiver.VerifiedMobile = tristate.True + } else { + receiver.VerifiedMobile = tristate.False + } + default: + var subContact SSubContact + subContact.Type = contact.ContactType + if subContact.Type == api.WEBCONSOLE { + webconsole = true + subContact.Contact = uid + } else { + subContact.Contact = contact.Contact + } + subContact.ReceiverID = uid + subContact.ParentContactType = api.MOBILE + if contact.Enabled == "1" { + subContact.Enabled = tristate.True + } else { + subContact.Enabled = tristate.False + } + if contact.Status == oldmodels.CONTACT_VERIFIED && len(contact.Contact) > 0 { + subContact.Verified = tristate.True + } else { + subContact.Verified = tristate.False + } + receiver.subContactCache[contact.ContactType] = &subContact + } + } + if !webconsole { + receiver.subContactCache[api.WEBCONSOLE] = &SSubContact{ + ReceiverID: receiver.Id, + Type: api.WEBCONSOLE, + Contact: receiver.Id, + ParentContactType: "", + Enabled: tristate.True, + Verified: tristate.True, + } + } + err := rm.TableSpec().InsertOrUpdate(ctx, &receiver) + if err != nil { + return errors.Wrap(err, "InsertOrUpdate") + } + err = receiver.PushCache(ctx) + if err != nil { + return errors.Wrap(err, "PushCache") + } + //delete old one + for _, contact := range contacts { + err := contact.Delete(ctx, userCred) + if err != nil { + return errors.Wrap(err, "Delete") + } + } + } + return nil +} + +func (rm *SReceiverManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ReceiverCreateInput) (api.ReceiverCreateInput, error) { + var err error + input.StatusStandaloneResourceCreateInput, err = rm.SStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StatusStandaloneResourceCreateInput) + if err != nil { + return input, err + } + // check uid + session := auth.GetSession(ctx, userCred, "", "") + if len(input.UID) > 0 { + userObj, err := modules.UsersV3.GetById(session, input.UID, nil) + if err != nil { + if jErr, ok := err.(*httputils.JSONClientError); ok { + if jErr.Code == 404 { + return input, httperrors.NewInputParameterError("no such user") + } + } + return input, err + } + uname, _ := userObj.GetString("name") + input.UName = uname + domainId, _ := userObj.GetString("domain_id") + input.ProjectDomainId = domainId + } else { + if len(input.UName) == 0 { + return input, httperrors.NewMissingParameterError("uid or uname") + } else { + userObj, err := modules.UsersV3.GetByName(session, input.UName, nil) + if err != nil { + if jErr, ok := err.(*httputils.JSONClientError); ok { + if jErr.Code == 404 { + return input, httperrors.NewInputParameterError("no such user") + } + } + return input, err + } + uid, _ := userObj.GetString("id") + input.UID = uid + domainId, _ := userObj.GetString("domain_id") + input.ProjectDomainId = domainId + } + } + // hack + input.Name = input.UName + // validate email + if ok := regutils.MatchEmail(input.Email); !ok { + return input, httperrors.NewInputParameterError("invalid email") + } + // validate mobile + if ok := regutils.MatchMobile(input.Mobile); !ok { + return input, httperrors.NewInputParameterError("invalid mobile") + } + return input, nil +} + +func (r *SReceiver) IsEnabledContactType(ct string) (bool, error) { + if utils.IsInStringArray(ct, AllRobotContactTypes) { + return true, nil + } + cts, err := r.GetEnabledContactTypes() + if err != nil { + return false, errors.Wrap(err, "GetEnabledContactTypes") + } + return utils.IsInStringArray(ct, cts), nil +} + +func (r *SReceiver) IsVerifiedContactType(ct string) (bool, error) { + if utils.IsInStringArray(ct, AllRobotContactTypes) { + return true, nil + } + cts, err := r.GetVerifiedContactTypes() + if err != nil { + return false, errors.Wrap(err, "GetVerifiedContactTypes") + } + return utils.IsInStringArray(ct, cts), nil +} + +func (r *SReceiver) GetEnabledContactTypes() ([]string, error) { + if err := r.PullCache(false); err != nil { + return nil, err + } + ret := make([]string, 0, 1) + // for email and mobile + if r.EnabledEmail.IsTrue() { + ret = append(ret, api.EMAIL) + } + if r.EnabledMobile.IsTrue() { + ret = append(ret, api.MOBILE) + } + for subct, subc := range r.subContactCache { + if subc.Enabled.IsTrue() { + ret = append(ret, subct) + } + } + return ret, nil +} + +func (r *SReceiver) setEnabledContactType(contactType string, enabled bool) { + switch contactType { + case api.EMAIL: + r.EnabledEmail = tristate.NewFromBool(enabled) + case api.MOBILE: + r.EnabledMobile = tristate.NewFromBool(enabled) + default: + if sc, ok := r.subContactCache[contactType]; ok { + sc.Enabled = tristate.NewFromBool(enabled) + } else { + r.subContactCache[contactType] = &SSubContact{ + Type: contactType, + ReceiverID: r.Id, + Enabled: tristate.NewFromBool(enabled), + } + } + } +} + +func (r *SReceiver) SetEnabledContactTypes(contactTypes []string) error { + if err := r.PullCache(false); err != nil { + return err + } + ctSet := sets.NewString(contactTypes...) + for _, ct := range AllContactTypes { + if ctSet.Has(ct) { + r.setEnabledContactType(ct, true) + } else { + r.setEnabledContactType(ct, false) + } + } + return nil +} + +func (r *SReceiver) MarkContactTypeVerified(contactType string) error { + if err := r.PullCache(false); err != nil { + return err + } + if sc, ok := r.subContactCache[contactType]; ok { + sc.Verified = tristate.True + } else { + r.subContactCache[contactType] = &SSubContact{ + ReceiverID: r.Id, + Verified: tristate.True, + } + } + return nil +} + +func (r *SReceiver) setVerifiedContactType(contactType string, enabled bool) { + switch contactType { + case api.EMAIL: + r.VerifiedEmail = tristate.NewFromBool(enabled) + case api.MOBILE: + r.VerifiedMobile = tristate.NewFromBool(enabled) + default: + if sc, ok := r.subContactCache[contactType]; ok { + sc.Verified = tristate.NewFromBool(enabled) + } else { + r.subContactCache[contactType] = &SSubContact{ + ReceiverID: r.Id, + Verified: tristate.NewFromBool(enabled), + } + } + } +} + +func (r *SReceiver) GetVerifiedContactTypes() ([]string, error) { + if err := r.PullCache(false); err != nil { + return nil, err + } + ret := make([]string, 0, 1) + // for email and mobile + if r.VerifiedEmail.IsTrue() { + ret = append(ret, api.EMAIL) + } + if r.VerifiedMobile.IsTrue() { + ret = append(ret, api.MOBILE) + } + for subct, subc := range r.subContactCache { + if subc.Verified.IsTrue() { + ret = append(ret, subct) + } + } + return ret, nil +} + +func (r *SReceiver) SetVerifiedContactTypes(contactTypes []string) error { + if err := r.PullCache(false); err != nil { + return err + } + ctSet := sets.NewString(contactTypes...) + for _, ct := range AllContactTypes { + if ctSet.Has(ct) { + r.setVerifiedContactType(ct, true) + } else { + r.setVerifiedContactType(ct, false) + } + } + return nil +} + +func (r *SReceiver) PullCache(force bool) error { + if !force && r.subContactCache != nil { + return nil + } + cache, err := SubContactManager.fetchMapByReceiverID(r.Id) + if err != nil { + return err + } + r.subContactCache = cache + return nil +} + +func (r *SReceiver) PushCache(ctx context.Context) error { + for subct, subc := range r.subContactCache { + err := SubContactManager.TableSpec().InsertOrUpdate(ctx, subc) + if err != nil { + return errors.Wrapf(err, "fail to save subcontact %q to db", subct) + } + } + return nil +} + +func (rm *SReceiverManager) EnabledContactFilter(contactType string, q *sqlchemy.SQuery) *sqlchemy.SQuery { + subQuery := SubContactManager.Query("receiver_id").Equals("type", contactType).IsTrue("enabled").SubQuery() + q = q.Join(subQuery, sqlchemy.Equals(subQuery.Field("receiver_id"), q.Field("id"))) + return q +} + +func (rm *SReceiverManager) VerifiedContactFilter(contactType string, q *sqlchemy.SQuery) *sqlchemy.SQuery { + subQuery := SubContactManager.Query("receiver_id").Equals("type", contactType).IsTrue("verified").SubQuery() + q = q.Join(subQuery, sqlchemy.Equals(subQuery.Field("receiver_id"), q.Field("id"))) + return q +} + +func (rm *SReceiverManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.ReceiverListInput) (*sqlchemy.SQuery, error) { + q, err := rm.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StatusStandaloneResourceListInput) + if err != nil { + return nil, err + } + q, err = rm.SDomainizedResourceBaseManager.ListItemFilter(ctx, q, userCred, input.DomainizedResourceListInput) + if err != nil { + return nil, err + } + q, err = rm.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledResourceBaseListInput) + if err != nil { + return nil, err + } + if len(input.UID) > 0 { + q = q.Equals("id", input.UID) + } + if len(input.UName) > 0 { + q = q.Equals("name", input.UName) + } + if len(input.EnabledContactType) > 0 { + q = rm.EnabledContactFilter(input.EnabledContactType, q) + } + if len(input.VerifiedContactType) > 0 { + q = rm.VerifiedContactFilter(input.VerifiedContactType, q) + } + return q, nil +} + +func (rm *SReceiverManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.ReceiverDetails { + sRows := rm.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + dRows := rm.SDomainizedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + rows := make([]api.ReceiverDetails, len(objs)) + var err error + for i := range rows { + rows[i].StatusStandaloneResourceDetails = sRows[i] + rows[i].DomainizedResourceInfo = dRows[i] + user := objs[i].(*SReceiver) + if rows[i].EnabledContactTypes, err = user.GetEnabledContactTypes(); err != nil { + log.Errorf("GetEnabledContactTypes: %v", err) + } + if rows[i].VerifiedContactTypes, err = user.GetVerifiedContactTypes(); err != nil { + log.Errorf("GetVerifiedContactTypes: %v", err) + } + } + return rows +} + +func (rm *SReceiverManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + q, err := rm.SStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err != nil { + return nil, err + } + q, err = rm.SDomainizedResourceBaseManager.QueryDistinctExtraField(q, field) + if err != nil { + return nil, err + } + return q, nil +} + +func (rm *SReceiverManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.ReceiverListInput) (*sqlchemy.SQuery, error) { + q, err := rm.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusStandaloneResourceListInput) + if err != nil { + return nil, err + } + q, err = rm.SDomainizedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.DomainizedResourceListInput) + return q, nil +} + +func (r *SReceiver) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + r.SStatusStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data) + // set status + r.SetStatus(userCred, api.RECEIVER_STATUS_PULLING, "") + logclient.AddActionLogWithContext(ctx, r, logclient.ACT_CREATE, nil, userCred, true) + task, err := taskman.TaskManager.NewTask(ctx, "SubcontactPullTask", r, userCred, nil, "", "") + if err != nil { + log.Errorf("ContactPullTask newTask error %v", err) + } else { + task.ScheduleRun(nil) + } +} + +func (r *SReceiver) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + err := r.SStatusStandaloneResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) + if err != nil { + return nil + } + var input api.ReceiverCreateInput + err = data.Unmarshal(&input) + if err != nil { + return err + } + // set id and name + r.Id = input.UID + r.Name = input.UName + r.DomainId = input.ProjectDomainId + if input.Enabled == nil { + r.Enabled = tristate.True + } + err = r.SetEnabledContactTypes(input.EnabledContactTypes) + if err != nil { + return errors.Wrap(err, "SetEnabledContactTypes") + } + err = r.PushCache(ctx) + if err != nil { + return errors.Wrap(err, "PushCache") + } + return nil +} + +func (r *SReceiver) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ReceiverUpdateInput) (api.ReceiverUpdateInput, error) { + var err error + input.StatusStandaloneResourceBaseUpdateInput, err = r.SStatusStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, input.StatusStandaloneResourceBaseUpdateInput) + if err != nil { + return input, err + } + // validate email + if ok := regutils.MatchEmail(input.Email); !ok { + return input, httperrors.NewInputParameterError("invalid email") + } + // validate mobile + if ok := regutils.MatchMobile(input.Mobile); !ok { + return input, httperrors.NewInputParameterError("invalid mobile") + } + return input, nil +} + +func (r *SReceiver) PreUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { + r.SStatusStandaloneResourceBase.PreUpdate(ctx, userCred, query, data) + var input api.ReceiverUpdateInput + err := data.Unmarshal(&input) + if err != nil { + log.Errorf("fail to unmarshal to ContactUpdateInput: %v", err) + } + err = r.PullCache(false) + if err != nil { + log.Errorf("PullCache: %v", err) + } + err = r.SetEnabledContactTypes(input.EnabledContactTypes) + if len(input.Email) != 0 { + r.VerifiedEmail = tristate.False + for _, c := range r.subContactCache { + if c.ParentContactType == input.Email { + c.Verified = tristate.False + } + } + } + if len(input.Mobile) != 0 { + r.VerifiedMobile = tristate.False + for _, c := range r.subContactCache { + if c.ParentContactType == input.Mobile { + c.Verified = tristate.False + } + } + } + err = r.PushCache(ctx) + if err != nil { + log.Errorf("PushCache: %v", err) + } + err = ReceiverManager.TableSpec().InsertOrUpdate(ctx, r) + if err != nil { + log.Errorf("InsertOrUpdate: %v", err) + } +} + +func (r *SReceiver) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { + r.SStatusStandaloneResourceBase.PostUpdate(ctx, userCred, query, data) + // set status + r.SetStatus(userCred, api.RECEIVER_STATUS_PULLING, "") + logclient.AddActionLogWithContext(ctx, r, logclient.ACT_UPDATE, nil, userCred, true) + task, err := taskman.TaskManager.NewTask(ctx, "SubcontactPullTask", r, userCred, nil, "", "") + if err != nil { + log.Errorf("ContactPullTask newTask error %v", err) + } else { + task.ScheduleRun(nil) + } +} + +func (r *SReceiver) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + err := r.PullCache(false) + if err != nil { + return err + } + for _, sc := range r.subContactCache { + err := sc.Delete(ctx, userCred) + if err != nil { + return err + } + } + return r.SStatusStandaloneResourceBase.Delete(ctx, userCred) +} + +func (r *SReceiver) IsOwner(userCred mcclient.TokenCredential) bool { + return r.Id == userCred.GetUserId() +} + +func (r *SReceiver) AllowPerformTriggerVerify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "trigger_verify") +} + +func (r *SReceiver) PerformTriggerVerify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ReceiverTriggerVerifyInput) (jsonutils.JSONObject, error) { + if len(input.ContactType) == 0 { + return nil, httperrors.NewMissingParameterError("contact_type") + } + if !utils.IsInStringArray(input.ContactType, []string{api.EMAIL, api.MOBILE}) { + return nil, httperrors.NewInputParameterError("not support such contact type %q", input.ContactType) + } + _, err := VerificationManager.Create(ctx, r.Id, input.ContactType) + if err == ErrVerifyFrequently { + return nil, httperrors.NewForbiddenError("Send verify message too frequently, please try again later") + } + if err != nil { + return nil, err + } + + params := jsonutils.Marshal(input).(*jsonutils.JSONDict) + task, err := taskman.TaskManager.NewTask(ctx, "VerificationSendTask", r, userCred, params, "", "") + if err != nil { + log.Errorf("ContactPullTask newTask error %v", err) + } else { + task.ScheduleRun(nil) + } + return nil, nil +} + +func (r *SReceiver) AllowPerformVerify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "verify") +} + +func (r *SReceiver) PerformVerify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ReceiverVerifyInput) (jsonutils.JSONObject, error) { + if len(input.ContactType) == 0 { + return nil, httperrors.NewMissingParameterError("contact_type") + } + if !utils.IsInStringArray(input.ContactType, []string{api.EMAIL, api.MOBILE}) { + return nil, httperrors.NewInputParameterError("not support such contact type %q", input.ContactType) + } + verification, err := VerificationManager.Get(r.Id, input.ContactType) + if err != nil { + return nil, err + } + if verification.Token != input.Token { + return nil, httperrors.NewInputParameterError("wrong token") + } + _, err = db.Update(r, func() error { + switch input.ContactType { + case api.EMAIL: + r.VerifiedEmail = tristate.True + case api.MOBILE: + r.VerifiedMobile = tristate.True + default: + // no way + } + return nil + }) + return nil, err +} + +func (r *SReceiver) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformEnableInput) bool { + return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "enable") +} + +func (r *SReceiver) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformEnableInput) (jsonutils.JSONObject, error) { + err := db.EnabledPerformEnable(r, ctx, userCred, true) + if err != nil { + return nil, errors.Wrap(err, "EnabledPerformEnable") + } + return nil, nil +} + +func (r *SReceiver) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformDisableInput) bool { + return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "disable") +} + +func (r *SReceiver) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformDisableInput) (jsonutils.JSONObject, error) { + err := db.EnabledPerformEnable(r, ctx, userCred, false) + if err != nil { + return nil, errors.Wrap(err, "EnabledPerformEnable") + } + return nil, nil +} + +// Implemente interface EventHandler +func (rm *SReceiverManager) OnAdd(obj *jsonutils.JSONDict) { + // do nothing + return +} + +func (rm *SReceiverManager) OnUpdate(oldObj, newObj *jsonutils.JSONDict) { + userId, _ := newObj.GetString("id") + receivers, err := rm.FetchByIDs(context.Background(), userId) + if err != nil { + log.Errorf("fail to FetchByIDs: %v", err) + return + } + receiver := &receivers[0] + uname, _ := newObj.GetString("name") + domainId, _ := newObj.GetString("domain_id") + if receiver.Name == uname && receiver.DomainId == domainId { + return + } + _, err = db.Update(receiver, func() error { + receiver.Name = uname + receiver.DomainId = domainId + return nil + }) + if err != nil { + log.Errorf("fail to update uname of contact %q: %v", receiver.Id, err) + } +} + +func (rm *SReceiverManager) OnDelete(obj *jsonutils.JSONDict) { + userId, _ := obj.GetString("id") + receivers, err := rm.FetchByIDs(context.Background(), userId) + if err != nil { + log.Errorf("fail to FetchByIDs: %v", err) + return + } + receiver := &receivers[0] + err = receiver.Delete(context.Background(), auth.GetAdminSession(context.Background(), "", "").GetToken()) + if err != nil { + log.Errorf("fail to delete contact %q: %v", receiver.Id, err) + } +} + +func (rm *SReceiverManager) StartWatchUserInKeystone() error { + adminSession := auth.GetAdminSession(context.Background(), "", "") + watchMan, err := informer.NewWatchManagerBySession(adminSession) + if err != nil { + return err + } + resMan := &modules.UsersV3 + return watchMan.For(resMan).AddEventHandler(context.Background(), rm) +} + +func (rm *SReceiverManager) FetchByIDs(ctx context.Context, ids ...string) ([]SReceiver, error) { + if len(ids) == 0 { + return nil, nil + } + var err error + q := rm.Query() + if len(ids) == 1 { + q = q.Equals("id", ids[0]) + } else { + q = q.In("id", ids) + } + contacts := make([]SReceiver, 0, len(ids)) + err = db.FetchModelObjects(rm, q, &contacts) + if err != nil { + return nil, err + } + return contacts, nil +} + +func (rm *SReceiverManager) FetchByIdOrNames(ctx context.Context, idOrNames ...string) ([]SReceiver, error) { + if len(idOrNames) == 0 { + return nil, nil + } + var err error + q := rm.Query() + if len(idOrNames) == 1 { + q = q.Filter(sqlchemy.OR( + sqlchemy.Equals(q.Field("id"), idOrNames[0]), + sqlchemy.Equals(q.Field("name"), idOrNames[0]), + )) + } else { + q = q.Filter(sqlchemy.OR( + sqlchemy.In(q.Field("id"), idOrNames), + sqlchemy.In(q.Field("name"), idOrNames), + )) + } + receivers := make([]SReceiver, 0, len(idOrNames)) + err = db.FetchModelObjects(rm, q, &receivers) + if err != nil { + return nil, err + } + return receivers, nil +} + +func (r *SReceiver) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.ReceiverDetails, error) { + return api.ReceiverDetails{}, nil +} + +func (r *SReceiver) SetContact(cType string, contact string) error { + if err := r.PullCache(false); err != nil { + return err + } + switch cType { + case api.EMAIL: + r.Email = contact + case api.MOBILE: + r.Mobile = contact + default: + if sc, ok := r.subContactCache[cType]; ok { + sc.Contact = contact + } + } + return nil +} + +func (r *SReceiver) GetContact(cType string) (string, error) { + if err := r.PullCache(false); err != nil { + return "", err + } + switch { + case cType == api.EMAIL: + return r.Email, nil + case cType == api.MOBILE: + return r.Mobile, nil + case utils.IsInStringArray(cType, AllRobotContactTypes): + return r.Mobile, nil + default: + if sc, ok := r.subContactCache[cType]; ok { + return sc.Contact, nil + } + } + return "", nil +} diff --git a/pkg/notify/models/receiver_notification.go b/pkg/notify/models/receiver_notification.go new file mode 100644 index 0000000000..0a9cc8a1c1 --- /dev/null +++ b/pkg/notify/models/receiver_notification.go @@ -0,0 +1,109 @@ +package models + +import ( + "context" + "time" + + api "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/mcclient" +) + +var ReceiverNotificationManager *SReceiverNotificationManager + +func init() { + db.InitManager(func() { + ReceiverNotificationManager = &SReceiverNotificationManager{ + SJointResourceBaseManager: db.NewJointResourceBaseManager( + SReceiverNotification{}, + "receivernotification_tbl", + "receivernotification", + "receivernotifications", + NotificationManager, + ReceiverManager, + ), + } + ReceiverNotificationManager.SetVirtualObject(ReceiverNotificationManager) + }) +} + +type SReceiverNotificationManager struct { + db.SJointResourceBaseManager +} + +// +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 + Contact string `width:"128" nullable:"false"` + SendAt time.Time `nullable:"false"` + SendBy string `width:"128" nullable:"false"` + Status string `width:"36" charset:"ascii"` + FailedReason string `width:"1024"` +} + +func (rnm *SReceiverNotificationManager) Create(ctx context.Context, userCred mcclient.TokenCredential, receiverID, notificationID string) (*SReceiverNotification, error) { + rn := &SReceiverNotification{ + ReceiverID: receiverID, + NotificationID: notificationID, + Status: api.RECEIVER_NOTIFICATION_RECEIVED, + SendBy: userCred.GetUserId(), + } + return rn, rnm.TableSpec().Insert(ctx, rn) +} + +func (rnm *SReceiverNotificationManager) GetMasterFieldName() string { + return "notification_id" +} + +func (rnm *SReceiverNotificationManager) GetSlaveFieldName() string { + return "receiver_id" +} + +func (rnm *SReceiverNotificationManager) CreateWithoutReceiver(ctx context.Context, userCred mcclient.TokenCredential, contact, notificationID string) (*SReceiverNotification, error) { + rn := &SReceiverNotification{ + NotificationID: notificationID, + Contact: contact, + Status: api.RECEIVER_NOTIFICATION_RECEIVED, + SendBy: userCred.GetUserId(), + } + return rn, rnm.TableSpec().Insert(ctx, rn) +} + +func (rn *SReceiverNotification) Receiver() (*SReceiver, error) { + q := ReceiverManager.Query().Equals("id", rn.ReceiverID) + var receiver SReceiver + err := q.First(&receiver) + if err != nil { + return nil, err + } + return &receiver, nil +} + +func (rn *SReceiverNotification) BeforeSend(ctx context.Context, sendTime time.Time) error { + if sendTime.IsZero() { + sendTime = time.Now() + } + _, err := db.Update(rn, func() error { + rn.SendAt = sendTime + rn.Status = api.RECEIVER_NOTIFICATION_SENT + return nil + }) + return err +} + +func (rn *SReceiverNotification) AfterSend(ctx context.Context, success bool, reason string) error { + _, err := db.Update(rn, func() error { + if success { + rn.Status = api.RECEIVER_NOTIFICATION_OK + } else { + rn.Status = api.RECEIVER_NOTIFICATION_FAIL + rn.FailedReason = reason + } + return nil + }) + return err +} diff --git a/pkg/notify/models/subcontact.go b/pkg/notify/models/subcontact.go new file mode 100644 index 0000000000..1ba160dd2a --- /dev/null +++ b/pkg/notify/models/subcontact.go @@ -0,0 +1,106 @@ +// 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 models + +import ( + "yunion.io/x/pkg/tristate" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" +) + +type SSubContactManager struct { + db.SStandaloneResourceBaseManager +} + +// +onecloud:swagger-gen-ignore +type SSubContact struct { + db.SStandaloneResourceBase + + // id of receiver user + ReceiverID string `width:"128" nullable:"false" index:"true"` + Type string `width:"16" nullable:"false" index:"true"` + Contact string `width:"128" nullable:"false"` + ParentContactType string `width:"16" nullable:"false"` + Enabled tristate.TriState `nullable:"false" default:"false"` + Verified tristate.TriState `nullable:"false" default:"false"` +} + +var SubContactManager *SSubContactManager + +var ( + vTrue = true + vFalse = false + + pTrue = &vTrue + pFalse = &vFalse +) + +func init() { + SubContactManager = &SSubContactManager{ + SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( + SSubContact{}, + "subcontacts_tbl", + "subcontact", + "subcontacts", + ), + } + SubContactManager.SetVirtualObject(SubContactManager) +} + +func (scm *SSubContactManager) fetchMapByReceiverID(receiverID string) (map[string]*SSubContact, error) { + q := scm.Query().Equals("receiver_id", receiverID) + scontacts := make([]SSubContact, 0, 3) + err := db.FetchModelObjects(scm, q, &scontacts) + if err != nil { + return nil, err + } + ret := make(map[string]*SSubContact, len(scontacts)) + for i := range scontacts { + ret[scontacts[i].Type] = &scontacts[i] + } + return ret, nil +} + +func (sc *SSubContact) Enable() error { + return sc.Update(nil, pTrue, nil) +} + +func (sc *SSubContact) Disable() error { + return sc.Update(nil, pFalse, nil) +} + +func (sc *SSubContact) Verify() error { + return sc.Update(nil, nil, pTrue) +} + +func (sc *SSubContact) Disverify() error { + return sc.Update(nil, nil, pFalse) +} + +func (sc *SSubContact) Update(contact *string, enabled *bool, verified *bool) error { + _, err := db.Update(sc, func() error { + if contact != nil { + sc.Contact = *contact + } + if enabled != nil { + sc.Enabled = tristate.NewFromBool(*enabled) + } + if verified != nil { + sc.Verified = tristate.NewFromBool(*verified) + } + return nil + }) + return err +} diff --git a/pkg/notify/models/mod_template.go b/pkg/notify/models/template.go similarity index 57% rename from pkg/notify/models/mod_template.go rename to pkg/notify/models/template.go index 1299a5fa65..0d76581cc7 100644 --- a/pkg/notify/models/mod_template.go +++ b/pkg/notify/models/template.go @@ -27,8 +27,10 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" + api "yunion.io/x/onecloud/pkg/apis/notify" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" @@ -40,31 +42,29 @@ import ( ) type STemplateManager struct { - SStandaloneResourceBaseManager + db.SStandaloneResourceBaseManager } var TemplateManager *STemplateManager func init() { TemplateManager = &STemplateManager{ - SStandaloneResourceBaseManager: NewStandaloneResourceBaseManager( + SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( STemplate{}, - "notify_t_template", + "template_tbl", "notifytemplate", "notifytemplates", ), } + TemplateManager.SetVirtualObject(TemplateManager) } const ( - TEMPLATE_TYPE_TITLE = "title" - TEMPLATE_TYPE_CONTENT = "content" - TEMPLATE_TYPE_REMOTE = "remote" - CONTACTTYPE_ALL = "all" + CONTACTTYPE_ALL = "all" ) type STemplate struct { - SStandaloneResourceBase + db.SStandaloneResourceBase ContactType string `width:"16" nullable:"false" create:"required" update:"user" list:"user"` Topic string `width:"20" nullable:"false" create:"required" update:"user" list:"user"` @@ -72,21 +72,18 @@ type STemplate struct { // title | content | remote TemplateType string `width:"10" nullable:"false" create:"required" update:"user" list:"user"` Content string `length:"text" nullable:"false" create:"required" get:"user" list:"user" update:"user"` + Example string `nullable:"false" created:"required" get:"user" list:"user" update:"user"` } const ( verifyUrlPath = "/email-verification/id/{0}/token/{1}?region=%s" + templatePath = "/opt/yunion/share/template" ) func (tm *STemplateManager) GetEmailUrl() string { - if len(options.Options.ApiServer) > 0 { - return httputils.JoinPath(options.Options.ApiServer, fmt.Sprintf(verifyUrlPath, options.Options.Region)) - } - return options.Options.VerifyEmailUrl + return httputils.JoinPath(options.Options.ApiServer, fmt.Sprintf(verifyUrlPath, options.Options.Region)) } -var templatePath = "/opt/yunion/share/template" - func (tm *STemplateManager) defaultTemplate() ([]STemplate, error) { templates := make([]STemplate, 0, 4) @@ -143,6 +140,12 @@ func (tm *STemplateManager) GetCompanyInfo(ctx context.Context) (SCompanyInfo, e return info, nil } +var ( + ForceInitType = []string{ + api.EMAIL, + } +) + func (tm *STemplateManager) InitializeData() error { templates, err := tm.defaultTemplate() if err != nil { @@ -151,12 +154,40 @@ func (tm *STemplateManager) InitializeData() error { for _, template := range templates { q := tm.Query().Equals("contact_type", template.ContactType).Equals("topic", template.Topic).Equals("template_type", template.TemplateType) count, _ := q.CountWithError() - if count > 0 { + if count > 0 && !utils.IsInStringArray(template.ContactType, ForceInitType) { continue } - err := tm.TableSpec().InsertOrUpdate(context.TODO(), &template) + if count == 0 { + err := tm.TableSpec().Insert(context.TODO(), &template) + if err != nil { + return errors.Wrap(err, "sqlchemy.TableSpec.Insert") + } + continue + } + oldTemplates := make([]STemplate, 0, 1) + err := db.FetchModelObjects(tm, q, &oldTemplates) if err != nil { - return errors.Wrap(err, "sqlchemy.TableSpec.InsertOrUpdate") + return errors.Wrap(err, "db.FetchModelObjects") + } + // delete addtion + var ( + ctx = context.Background() + userCred = auth.AdminCredential() + ) + for i := 1; i < len(oldTemplates); i++ { + err := oldTemplates[i].Delete(ctx, userCred) + if err != nil { + return errors.Wrap(err, "STemplate.Delete") + } + } + // update + oldTemplate := &oldTemplates[0] + _, err = db.Update(oldTemplate, func() error { + oldTemplate.Content = template.Content + return nil + }) + if err != nil { + return errors.Wrap(err, "db.Update") } } return nil @@ -182,19 +213,19 @@ func (tm *STemplateManager) NotifyFilter(contactType, topic, msg string) (params for _, template := range templates { var title, content string switch template.TemplateType { - case TEMPLATE_TYPE_TITLE: + case api.TEMPLATE_TYPE_TITLE: title, err = template.Execute(msg) if err != nil { return } params.Title = title - case TEMPLATE_TYPE_CONTENT: + case api.TEMPLATE_TYPE_CONTENT: content, err = template.Execute(msg) if err != nil { return } params.Message = content - case TEMPLATE_TYPE_REMOTE: + case api.TEMPLATE_TYPE_REMOTE: params.RemoteTemplate = template.Content params.Message = msg default: @@ -223,29 +254,69 @@ func (tm *STemplate) Execute(str string) (string, error) { return buffer.String(), nil } -func (manager *STemplateManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, - ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - - ty, _ := data.GetString("template_type") - if ty != TEMPLATE_TYPE_TITLE && ty != TEMPLATE_TYPE_CONTENT && ty != TEMPLATE_TYPE_REMOTE { - return nil, httperrors.NewInputParameterError("no such support for tempalte type %s", ty) +func (tm *STemplateManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.TemplateCreateInput) (api.TemplateCreateInput, error) { + if !utils.IsInStringArray(input.TemplateType, []string{ + api.TEMPLATE_TYPE_CONTENT, api.TEMPLATE_TYPE_REMOTE, api.TEMPLATE_TYPE_TITLE, + }) { + return input, httperrors.NewInputParameterError("no such support for tempalte type %s", input.TemplateType) } - return data, nil + if input.TemplateType != api.TEMPLATE_TYPE_REMOTE { + if err := tm.validate(input.Content, input.Example); err != nil { + return input, httperrors.NewInputParameterError(err.Error()) + } + } + if len(input.Name) == 0 { + input.Name = fmt.Sprintf("%s-%s-%s", input.ContactType, input.Topic, input.TemplateType) + } + return input, nil } -func (self *STemplateManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { - queryDict := query.(*jsonutils.JSONDict) - if queryDict.Contains("topic") { - val, _ := queryDict.GetString("topic") - q = q.Equals("topic", val) +func (tm *STemplateManager) validate(template string, example string) error { + // check example availability + tem, err := ptem.New("tmp").Parse(template) + if err != nil { + return errors.Wrap(err, "invalid template") } - if queryDict.Contains("template_type") { - val, _ := queryDict.GetString("template_type") - q = q.Equals("template_type", val) + var buffer bytes.Buffer + tmpMap := make(map[string]interface{}) + err = json.Unmarshal([]byte(example), &tmpMap) + if err != nil { + return errors.Wrap(err, "invalid example") } - if queryDict.Contains("contact_type") { - val, _ := queryDict.GetString("contact_type") - q = q.Equals("contact_type", val) + err = tem.Execute(&buffer, tmpMap) + if err != nil { + return errors.Wrap(err, "invalid example") + } + return nil +} + +func (tm *STemplateManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.TemplateListInput) (*sqlchemy.SQuery, error) { + q, err := tm.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput) + if err != nil { + return nil, err + } + if len(input.Topic) > 0 { + q = q.Equals("topic", input.Topic) + } + if len(input.TemplateType) > 0 { + q = q.Equals("template_type", input.TemplateType) + } + if len(input.ContactType) > 0 { + q = q.Equals("contact_type", input.ContactType) } return q, nil } + +func (t *STemplate) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.TemplateUpdateInput) (api.TemplateUpdateInput, error) { + if t.TemplateType == api.TEMPLATE_TYPE_REMOTE { + return input, nil + } + if err := TemplateManager.validate(input.Content, input.Example); err != nil { + return input, httperrors.NewInputParameterError(err.Error()) + } + return input, nil +} + +func (t *STemplate) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (api.TemplateDetails, error) { + return api.TemplateDetails{}, nil +} diff --git a/pkg/notify/models/verify.go b/pkg/notify/models/verify.go new file mode 100644 index 0000000000..b171e62496 --- /dev/null +++ b/pkg/notify/models/verify.go @@ -0,0 +1,108 @@ +// 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 models + +import ( + "context" + "database/sql" + "fmt" + "math/rand" + "time" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/notify/options" +) + +type SVerificationManager struct { + db.SStandaloneResourceBaseManager +} + +var VerificationManager *SVerificationManager + +func init() { + VerificationManager = &SVerificationManager{ + SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( + SVerification{}, + "verification_tbl", + "verification", + "verifications", + ), + } + VerificationManager.SetVirtualObject(VerificationManager) +} + +// +onecloud:swagger-gen-ignore +type SVerification struct { + db.SStandaloneResourceBase + + ReceiverId string `width:"128" nullable:"false"` + ContactType string `width:"16" nullable:"false"` + Token string `width:"200" nullable:"false"` +} + +var ErrVerifyFrequently = errors.Error("Send validation messages too frequently") + +func (vm *SVerificationManager) generateVerifyToken() string { + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) + token := fmt.Sprintf("%06v", rnd.Int31n(1000000)) + return token +} + +func (vm *SVerificationManager) Create(ctx context.Context, receiverId, contactType string) (*SVerification, error) { + // try to reuse + ret, err := vm.Get(receiverId, contactType) + if err != nil && errors.Cause(err) != sql.ErrNoRows { + return nil, err + } + if ret == nil { + ret = &SVerification{ + ReceiverId: receiverId, + ContactType: contactType, + Token: vm.generateVerifyToken(), + } + err := vm.TableSpec().Insert(ctx, ret) + if err != nil { + return nil, err + } + } else { + now := time.Now() + if now.Before(ret.CreatedAt.Add(time.Duration(options.Options.VerifyExpireInterval) * time.Minute)) { + return nil, ErrVerifyFrequently + } + _, err := db.Update(ret, func() error { + ret.Token = vm.generateVerifyToken() + ret.CreatedAt = now + ret.UpdatedAt = now + return nil + }) + if err != nil { + return nil, err + } + } + return ret, nil +} + +func (vm *SVerificationManager) Get(receiverId, contactType string) (*SVerification, error) { + q := vm.Query().Equals("receiver_id", receiverId).Equals("contact_type", contactType) + var verification SVerification + err := q.First(&verification) + if err != nil { + return nil, err + } + verification.SetModelManager(vm, &verification) + return &verification, nil +} diff --git a/pkg/notify/models/worker.go b/pkg/notify/models/worker.go deleted file mode 100644 index 3c90fbe1b9..0000000000 --- a/pkg/notify/models/worker.go +++ /dev/null @@ -1,184 +0,0 @@ -// 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 models - -import ( - "context" - "fmt" - "strings" - "time" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/errors" - - "yunion.io/x/onecloud/pkg/appsrv" - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/notify/utils" -) - -var workMan *appsrv.SWorkerManager - -func init() { - workMan = appsrv.NewWorkerManager("NotifyWokerManager", 16, 512, false) -} - -func Send(notifications []*SNotification, userCred mcclient.TokenCredential, contacts []string) { - - for i := range notifications { - notification, contact := notifications[i], contacts[i] - workMan.Run(func() { - sendone(context.Background(), userCred, notification, contact) - }, nil, nil) - } -} - -func sendone(ctx context.Context, userCred mcclient.TokenCredential, notification *SNotification, contact string) { - err := notification.SetSentAndTime(userCred) - if err != nil { - log.Errorf("Change notification's status failed.") - return - } - err = NotifyService.Send(ctx, notification.ContactType, contact, notification.Topic, notification.Msg, - notification.Priority) - if err != nil { - log.Errorf("Send notification failed: %s.", err.Error()) - notification.SetStatus(userCred, NOTIFY_FAIL, err.Error()) - } else { - log.Debugf("send notification successfully") - notification.SetStatus(userCred, NOTIFY_OK, "") - } -} - -func RestartService(config map[string]string, serviceName string) { - workMan.Run(func() { - NotifyService.RestartService(context.Background(), config, serviceName) - }, nil, nil) -} - -func SendVerifyMessage(ctx context.Context, userCred mcclient.TokenCredential, verify *SVerify, - contact *SContact) error { - var ( - err error - msg string - ) - info, err := TemplateManager.GetCompanyInfo(ctx) - if err != nil { - log.Errorf("unable to try to get company info: %s", err.Error()) - } - processId, token := verify.ID, verify.Token - if contact.ContactType == "email" { - emailUrl := strings.Replace(TemplateManager.GetEmailUrl(), "{0}", processId, 1) - emailUrl = strings.Replace(emailUrl, "{1}", token, 1) - - // get uName - uName, err := utils.GetUsernameByID(ctx, contact.UID) - if err != nil || len(uName) == 0 { - uName = "用户" - } - data := struct { - Name string - Link string - SCompanyInfo - }{ - Name: uName, - Link: emailUrl, - SCompanyInfo: info, - } - msg = jsonutils.Marshal(data).String() - } else if contact.ContactType == "mobile" { - msg = fmt.Sprintf(`{"code": "%s"}`, token) - } else { - // todo - return nil - } - - err = NotifyService.Send(ctx, contact.ContactType, contact.Contact, "verify", msg, "") - if err != nil { - verify.SetStatus(userCred, VERIFICATION_SENT_FAIL, "") - // set contact's status as "init" - contact.SetStatus(userCred, CONTACT_INIT, "send verify message failed") - log.Errorf("Send verify message failed: %s.", err.Error()) - return errors.Wrap(err, "Send Verify Message Failed") - } - verify.SetStatus(userCred, VERIFICATION_SENT, "") - return nil -} - -func PullContact(uid string, contactTypes []string) { - for i := range contactTypes { - ct := contactTypes[i] - workMan.Run(func() { - pullContact(context.Background(), uid, ct) - }, nil, nil) - } -} - -func pullContact(ctx context.Context, uid string, contactType string) { - contacts, err := ContactManager.FetchByUIDAndCType(uid, []string{MOBILE, contactType}) - if err != nil { - log.Errorf("fetch contacts error") - } - if len(contacts) == 0 { - return - } - var mobileContact, subContact *SContact - for i := range contacts { - if contacts[i].ContactType == MOBILE { - mobileContact = &contacts[i] - } else { - subContact = &contacts[i] - } - } - if mobileContact == nil { - return - } - - userid, err := NotifyService.ContactByMobile(ctx, mobileContact.Contact, contactType) - if err != nil { - log.Errorf("fetch %s contact by mobile failed: %s", contactType, err.Error()) - } - if subContact != nil { - subContact.SetModelManager(ContactManager, subContact) - origin := subContact.Contact - _, err := db.Update(subContact, func() error { - subContact.Contact = userid - subContact.VerifiedAt = time.Now() - if subContact.Status != CONTACT_VERIFIED { - subContact.Status = CONTACT_VERIFIED - } - return nil - }) - if err != nil { - log.Errorf("update %s contact userid %s => %s failed", contactType, origin, userid) - } - return - } - - contact := SContact{ - UID: uid, - ContactType: contactType, - Contact: userid, - Enabled: "1", - VerifiedAt: time.Now(), - } - contact.Status = CONTACT_VERIFIED - - err = ContactManager.TableSpec().Insert(ctx, &contact) - if err != nil { - log.Errorf("create new %s contact failed", contactType) - } -} diff --git a/pkg/notify/models/base.go b/pkg/notify/oldmodels/base.go similarity index 99% rename from pkg/notify/models/base.go rename to pkg/notify/oldmodels/base.go index f94879ecc8..d2b72685ee 100644 --- a/pkg/notify/models/base.go +++ b/pkg/notify/oldmodels/base.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package models +package oldmodels import ( "context" diff --git a/pkg/notify/interface/doc.go b/pkg/notify/oldmodels/doc.go similarity index 88% rename from pkg/notify/interface/doc.go rename to pkg/notify/oldmodels/doc.go index 203113f4d6..1953161f39 100644 --- a/pkg/notify/interface/doc.go +++ b/pkg/notify/oldmodels/doc.go @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package _interface // import "yunion.io/x/onecloud/pkg/notify/interface" +package oldmodels // import "yunion.io/x/onecloud/pkg/notify/oldmodels" diff --git a/pkg/notify/oldmodels/mod_config.go b/pkg/notify/oldmodels/mod_config.go new file mode 100644 index 0000000000..a70b06fcba --- /dev/null +++ b/pkg/notify/oldmodels/mod_config.go @@ -0,0 +1,44 @@ +// 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 oldmodels + +type SConfigManager struct { + SStatusStandaloneResourceBaseManager +} + +var ConfigManager *SConfigManager + +func init() { + ConfigManager = &SConfigManager{ + SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager( + SConfig{}, + "notify_t_config", + "oldconfig", + "oldconfigs", + ), + } + ConfigManager.SetVirtualObject(ConfigManager) +} + +// SConfig is a table which storage (k,v) and its type. +// The three important concepts are key, value and type. +// Key and type uniquely identify a value. +type SConfig struct { + SStatusStandaloneResourceBase + + Type string `width:"15" nullable:"false" create:"required" list:"user"` + KeyText string `width:"30" nullable:"false" create:"required" list:"user"` + ValueText string `width:"256" nullable:"false" create:"required" list:"user"` +} diff --git a/pkg/notify/oldmodels/mod_contact.go b/pkg/notify/oldmodels/mod_contact.go new file mode 100644 index 0000000000..f33ec8b615 --- /dev/null +++ b/pkg/notify/oldmodels/mod_contact.go @@ -0,0 +1,53 @@ +// 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 oldmodels + +import ( + "time" +) + +type SContactManager struct { + SStatusStandaloneResourceBaseManager +} + +var ContactManager *SContactManager + +func init() { + ContactManager = &SContactManager{ + SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager( + SContact{}, + "notify_t_contacts", + "contact", + "contacts", + ), + } + ContactManager.SetVirtualObject(ContactManager) +} + +const ( + CONTACT_INIT = "init" // Contact's status is init which means no verifying + CONTACT_VERIFYING = "verifying" // Contact's status is verifying + CONTACT_VERIFIED = "verified" // Contact's status is verified +) + +type SContact struct { + SStatusStandaloneResourceBase + + UID string `width:"128" nullable:"false" create:"required" update:"user" list:"user" get:"user"` + ContactType string `width:"16" nullable:"false" create:"required" update:"user"` + Contact string `width:"64" nullable:"false" create:"required" update:"user"` + Enabled string `width:"5" nullable:"false" default:"1" create:"optional" update:"user"` + VerifiedAt time.Time `update:"user"` +} diff --git a/pkg/notify/oldmodels/mod_notification.go b/pkg/notify/oldmodels/mod_notification.go new file mode 100644 index 0000000000..d2eacf82a3 --- /dev/null +++ b/pkg/notify/oldmodels/mod_notification.go @@ -0,0 +1,52 @@ +// 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 oldmodels + +import ( + "time" +) + +type SNotificationManager struct { + SStatusStandaloneResourceBaseManager +} + +var NotificationManager *SNotificationManager + +func init() { + NotificationManager = &SNotificationManager{ + SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager( + SNotification{}, + "notify_t_notification", + "oldnotification", + "oldnotifications", + ), + } + NotificationManager.SetVirtualObject(NotificationManager) +} + +type SNotification struct { + SStatusStandaloneResourceBase + + UID string `width:"128" nullable:"false" create:"required"` + ContactType string `width:"16" nullable:"false" create:"required" list:"user" index:"true"` + Topic string `width:"128" nullable:"true" create:"optional" list:"user"` + Priority string `width:"16" nullable:"true" create:"optional" list:"user"` + Msg string `create:"required"` + ReceivedAt time.Time `nullable:"true" list:"user" create:"optional"` + SendAt time.Time `nullable:"false"` + SendBy string `width:"128" nullable:"false"` + // ClusterID identify message with same topic, msg, priority + ClusterID string `width:"128" charset:"ascii" primary:"true" create:"optional" list:"user" get:"user"` +} diff --git a/pkg/notify/oldmodels/mod_template.go b/pkg/notify/oldmodels/mod_template.go new file mode 100644 index 0000000000..c25688f290 --- /dev/null +++ b/pkg/notify/oldmodels/mod_template.go @@ -0,0 +1,51 @@ +// 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 oldmodels + +type STemplateManager struct { + SStandaloneResourceBaseManager +} + +var TemplateManager *STemplateManager + +func init() { + TemplateManager = &STemplateManager{ + SStandaloneResourceBaseManager: NewStandaloneResourceBaseManager( + STemplate{}, + "notify_t_template", + "oldtemplate", + "oldtemplates", + ), + } + TemplateManager.SetVirtualObject(TemplateManager) +} + +const ( + TEMPLATE_TYPE_TITLE = "title" + TEMPLATE_TYPE_CONTENT = "content" + TEMPLATE_TYPE_REMOTE = "remote" + CONTACTTYPE_ALL = "all" +) + +type STemplate struct { + SStandaloneResourceBase + + ContactType string `width:"16" nullable:"false" create:"required" update:"user" list:"user"` + Topic string `width:"20" nullable:"false" create:"required" update:"user" list:"user"` + + // title | content | remote + TemplateType string `width:"10" nullable:"false" create:"required" update:"user" list:"user"` + Content string `length:"text" nullable:"false" create:"required" get:"user" list:"user" update:"user"` +} diff --git a/pkg/notify/models/standalone.go b/pkg/notify/oldmodels/standalone.go similarity index 99% rename from pkg/notify/models/standalone.go rename to pkg/notify/oldmodels/standalone.go index 1c20a01bd0..437d81a66f 100644 --- a/pkg/notify/models/standalone.go +++ b/pkg/notify/oldmodels/standalone.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package models +package oldmodels import ( "database/sql" diff --git a/pkg/notify/models/statusstandalone.go b/pkg/notify/oldmodels/statusstandalone.go similarity index 99% rename from pkg/notify/models/statusstandalone.go rename to pkg/notify/oldmodels/statusstandalone.go index 64908a30e9..d6851ad10b 100644 --- a/pkg/notify/models/statusstandalone.go +++ b/pkg/notify/oldmodels/statusstandalone.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package models +package oldmodels import ( "context" diff --git a/pkg/notify/cache/usercache.go b/pkg/notify/oldmodels/usercache.go similarity index 97% rename from pkg/notify/cache/usercache.go rename to pkg/notify/oldmodels/usercache.go index 160de8a664..33436ed60f 100644 --- a/pkg/notify/cache/usercache.go +++ b/pkg/notify/oldmodels/usercache.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cache +package oldmodels import ( "context" @@ -42,7 +42,7 @@ var UserCacheManager *SUserCacheManager func init() { dbUserCacheManager := db.SUserCacheManager{ SKeystoneCacheObjectManager: db.NewKeystoneCacheObjectManager( - db.SUser{}, "users_cache_tbl", "user", "users"), + db.SUser{}, "users_cache_tbl", "olduser", "oldusers"), } UserCacheManager = &SUserCacheManager{ dbUserCacheManager, diff --git a/pkg/notify/options/options.go b/pkg/notify/options/options.go index c62487f7fa..26113bddba 100644 --- a/pkg/notify/options/options.go +++ b/pkg/notify/options/options.go @@ -22,17 +22,16 @@ type NotifyOption struct { common_options.CommonOptions common_options.DBOptions - DingtalkEnabled bool `help:"Enable dingtalk"` - SocketFileDir string `help:"Socket file directory" default:"/etc/yunion/notify"` - UpdateInterval int `help:"Update send services interval(unit:min)" default:"30"` - // VerifyEmailUrlPath string `help:"url of verify email" json:"verify_email_url_path"` + SocketFileDir string `help:"Socket file directory" default:"/etc/yunion/socket"` + UpdateInterval int `help:"Update send services interval(unit:min)" default:"30"` - // Deprecated - VerifyEmailUrl string `help:"url of verify email" json:"verify_email_url"` - - ReSendScope int `help:"Resend all messages that have not been sent successfully within ReSendScope seconds" default:"30"` + ReSendScope int `help:"Resend all messages that have not been sent successfully within ReSendScope seconds" default:"60"` + MaxSendTimes int `help:"Resend all messages whose sendTimes less than MaxSendTimes" default:"2"` InitNotificationScope int `help:"initialize data of notification with in InitNotificationScope hours" default:"100"` + MaxSyncNotification int `help:"The max number of notification sync from old data source" default:"1000"` + + VerifyExpireInterval int `help:"expire interval of verify message; minutes" default:"5"` } var Options NotifyOption diff --git a/pkg/notify/rpc/apis/send_client.go b/pkg/notify/rpc/apis/send_client.go index 6391bb1a2d..64ad790004 100644 --- a/pkg/notify/rpc/apis/send_client.go +++ b/pkg/notify/rpc/apis/send_client.go @@ -58,3 +58,9 @@ func (c *SendNotificationClient) UseridByMobile(ctx context.Context, in *UseridB defer cancel() return c.sendAgentClient.UseridByMobile(ctx, in, opts...) } + +func (c *SendNotificationClient) BatchSend(ctx context.Context, in *BatchSendParams, opts ...grpc.CallOption) (*BatchSendReply, error) { + ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) + defer cancel() + return c.sendAgentClient.BatchSend(ctx, in, opts...) +} diff --git a/pkg/notify/rpc/apis/send_server.pb.go b/pkg/notify/rpc/apis/send_server.pb.go index 40ddf6bf3c..c3106b46b3 100644 --- a/pkg/notify/rpc/apis/send_server.pb.go +++ b/pkg/notify/rpc/apis/send_server.pb.go @@ -1,17 +1,3 @@ -// 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. - // Code generated by protoc-gen-go. DO NOT EDIT. // source: send_server.proto @@ -313,6 +299,163 @@ func (m *ValidateConfigReply) GetMsg() string { return "" } +type BatchSendParams struct { + Contacts []string `protobuf:"bytes,1,rep,name=Contacts,proto3" json:"Contacts,omitempty"` + Title string `protobuf:"bytes,2,opt,name=Title,proto3" json:"Title,omitempty"` + Message string `protobuf:"bytes,3,opt,name=Message,proto3" json:"Message,omitempty"` + Priority string `protobuf:"bytes,4,opt,name=Priority,proto3" json:"Priority,omitempty"` + RemoteTemplate string `protobuf:"bytes,5,opt,name=RemoteTemplate,proto3" json:"RemoteTemplate,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *BatchSendParams) Reset() { *m = BatchSendParams{} } +func (m *BatchSendParams) String() string { return proto.CompactTextString(m) } +func (*BatchSendParams) ProtoMessage() {} +func (*BatchSendParams) Descriptor() ([]byte, []int) { + return fileDescriptor_63fdd68f7eb311f9, []int{6} +} + +func (m *BatchSendParams) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_BatchSendParams.Unmarshal(m, b) +} +func (m *BatchSendParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_BatchSendParams.Marshal(b, m, deterministic) +} +func (m *BatchSendParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_BatchSendParams.Merge(m, src) +} +func (m *BatchSendParams) XXX_Size() int { + return xxx_messageInfo_BatchSendParams.Size(m) +} +func (m *BatchSendParams) XXX_DiscardUnknown() { + xxx_messageInfo_BatchSendParams.DiscardUnknown(m) +} + +var xxx_messageInfo_BatchSendParams proto.InternalMessageInfo + +func (m *BatchSendParams) GetContacts() []string { + if m != nil { + return m.Contacts + } + return nil +} + +func (m *BatchSendParams) GetTitle() string { + if m != nil { + return m.Title + } + return "" +} + +func (m *BatchSendParams) GetMessage() string { + if m != nil { + return m.Message + } + return "" +} + +func (m *BatchSendParams) GetPriority() string { + if m != nil { + return m.Priority + } + return "" +} + +func (m *BatchSendParams) GetRemoteTemplate() string { + if m != nil { + return m.RemoteTemplate + } + return "" +} + +type FailedRecord struct { + Contact string `protobuf:"bytes,1,opt,name=Contact,proto3" json:"Contact,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=Reason,proto3" json:"Reason,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *FailedRecord) Reset() { *m = FailedRecord{} } +func (m *FailedRecord) String() string { return proto.CompactTextString(m) } +func (*FailedRecord) ProtoMessage() {} +func (*FailedRecord) Descriptor() ([]byte, []int) { + return fileDescriptor_63fdd68f7eb311f9, []int{7} +} + +func (m *FailedRecord) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_FailedRecord.Unmarshal(m, b) +} +func (m *FailedRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_FailedRecord.Marshal(b, m, deterministic) +} +func (m *FailedRecord) XXX_Merge(src proto.Message) { + xxx_messageInfo_FailedRecord.Merge(m, src) +} +func (m *FailedRecord) XXX_Size() int { + return xxx_messageInfo_FailedRecord.Size(m) +} +func (m *FailedRecord) XXX_DiscardUnknown() { + xxx_messageInfo_FailedRecord.DiscardUnknown(m) +} + +var xxx_messageInfo_FailedRecord proto.InternalMessageInfo + +func (m *FailedRecord) GetContact() string { + if m != nil { + return m.Contact + } + return "" +} + +func (m *FailedRecord) GetReason() string { + if m != nil { + return m.Reason + } + return "" +} + +type BatchSendReply struct { + FailedRecords []*FailedRecord `protobuf:"bytes,1,rep,name=FailedRecords,proto3" json:"FailedRecords,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *BatchSendReply) Reset() { *m = BatchSendReply{} } +func (m *BatchSendReply) String() string { return proto.CompactTextString(m) } +func (*BatchSendReply) ProtoMessage() {} +func (*BatchSendReply) Descriptor() ([]byte, []int) { + return fileDescriptor_63fdd68f7eb311f9, []int{8} +} + +func (m *BatchSendReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_BatchSendReply.Unmarshal(m, b) +} +func (m *BatchSendReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_BatchSendReply.Marshal(b, m, deterministic) +} +func (m *BatchSendReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_BatchSendReply.Merge(m, src) +} +func (m *BatchSendReply) XXX_Size() int { + return xxx_messageInfo_BatchSendReply.Size(m) +} +func (m *BatchSendReply) XXX_DiscardUnknown() { + xxx_messageInfo_BatchSendReply.DiscardUnknown(m) +} + +var xxx_messageInfo_BatchSendReply proto.InternalMessageInfo + +func (m *BatchSendReply) GetFailedRecords() []*FailedRecord { + if m != nil { + return m.FailedRecords + } + return nil +} + func init() { proto.RegisterType((*SendParams)(nil), "apis.SendParams") proto.RegisterType((*UpdateConfigParams)(nil), "apis.UpdateConfigParams") @@ -321,37 +464,47 @@ func init() { proto.RegisterType((*Empty)(nil), "apis.Empty") proto.RegisterType((*UseridByMobileReply)(nil), "apis.UseridByMobileReply") proto.RegisterType((*ValidateConfigReply)(nil), "apis.ValidateConfigReply") + proto.RegisterType((*BatchSendParams)(nil), "apis.BatchSendParams") + proto.RegisterType((*FailedRecord)(nil), "apis.FailedRecord") + proto.RegisterType((*BatchSendReply)(nil), "apis.BatchSendReply") } func init() { proto.RegisterFile("send_server.proto", fileDescriptor_63fdd68f7eb311f9) } var fileDescriptor_63fdd68f7eb311f9 = []byte{ - // 398 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0xcf, 0x6a, 0xdb, 0x40, - 0x10, 0xc6, 0x51, 0xfc, 0x47, 0xc9, 0x24, 0x98, 0x74, 0x13, 0xca, 0x56, 0xa7, 0x20, 0x70, 0xf1, - 0xa5, 0x3a, 0xb8, 0x14, 0x8a, 0x2f, 0xc5, 0x35, 0xa6, 0x27, 0x83, 0x51, 0xed, 0x5e, 0xcb, 0xda, - 0x9a, 0x8a, 0xa5, 0x92, 0x56, 0xec, 0xae, 0x0d, 0x7a, 0x8c, 0xbe, 0x49, 0x5f, 0xaf, 0xb7, 0xb2, - 0x7f, 0x54, 0x5b, 0xad, 0x9b, 0x9b, 0x7e, 0xdf, 0xcc, 0x37, 0xcc, 0x7c, 0x5a, 0x78, 0xa1, 0xb0, - 0xca, 0xbe, 0x2a, 0x94, 0x47, 0x94, 0x49, 0x2d, 0x85, 0x16, 0xa4, 0xcf, 0x6a, 0xae, 0xe2, 0x9f, - 0x01, 0xc0, 0x67, 0xac, 0xb2, 0x35, 0x93, 0xac, 0x54, 0x84, 0x42, 0xb8, 0x10, 0x95, 0x66, 0x7b, - 0x4d, 0x83, 0xa7, 0x60, 0x72, 0x93, 0xb6, 0x48, 0x1e, 0x61, 0xb0, 0x11, 0x35, 0xdf, 0xd3, 0x2b, - 0xab, 0x3b, 0xb0, 0x2a, 0xd7, 0x05, 0xd2, 0x9e, 0x57, 0x0d, 0x98, 0x29, 0x2b, 0x54, 0x8a, 0xe5, - 0x48, 0xfb, 0x6e, 0x8a, 0x47, 0x12, 0xc1, 0xf5, 0x5a, 0x72, 0x21, 0xb9, 0x6e, 0xe8, 0xc0, 0x96, - 0xfe, 0x30, 0x79, 0x0d, 0xa3, 0x14, 0x4b, 0xa1, 0x71, 0x83, 0x65, 0x5d, 0x30, 0x8d, 0x74, 0x68, - 0x3b, 0xfe, 0x52, 0xe3, 0x1f, 0x01, 0x90, 0x6d, 0x9d, 0x31, 0x8d, 0x0b, 0x51, 0x7d, 0xe3, 0xb9, - 0x5f, 0xfd, 0x03, 0x84, 0x7b, 0xcb, 0x8a, 0x06, 0x4f, 0xbd, 0xc9, 0xed, 0x74, 0x9c, 0x98, 0x0b, - 0x93, 0x7f, 0x5b, 0x13, 0x07, 0x6a, 0x59, 0x69, 0xd9, 0xa4, 0xad, 0x2b, 0x9a, 0xc1, 0xdd, 0x79, - 0x81, 0xdc, 0x43, 0xef, 0x3b, 0x36, 0x3e, 0x07, 0xf3, 0x69, 0xae, 0x3d, 0xb2, 0xe2, 0x80, 0x6d, - 0x06, 0x16, 0x66, 0x57, 0xef, 0x83, 0x38, 0x81, 0xc7, 0xad, 0x42, 0xc9, 0xb3, 0x8f, 0xcd, 0x4a, - 0xec, 0x78, 0x81, 0x7e, 0xa9, 0x97, 0x30, 0x2c, 0x2d, 0xfb, 0x31, 0x9e, 0xe2, 0x10, 0x06, 0xcb, - 0xb2, 0xd6, 0x4d, 0xfc, 0x06, 0x1e, 0xba, 0xc6, 0x14, 0xeb, 0xa2, 0x31, 0xbe, 0x83, 0x95, 0x5b, - 0x9f, 0xa3, 0x78, 0x0e, 0x0f, 0x5f, 0x58, 0xc1, 0x4f, 0x17, 0xb9, 0x76, 0x0a, 0x21, 0x57, 0xb6, - 0x60, 0xfb, 0xaf, 0xd3, 0x16, 0xcd, 0x11, 0xa5, 0xca, 0xfd, 0xc2, 0xe6, 0x73, 0xfa, 0x2b, 0x80, - 0x1b, 0xf3, 0xc7, 0xe7, 0x39, 0x56, 0x9a, 0x8c, 0xa1, 0x6f, 0x80, 0xdc, 0xbb, 0xb0, 0x4e, 0x4f, - 0x21, 0xba, 0x75, 0x8a, 0x5d, 0x93, 0xbc, 0x83, 0xbb, 0xf3, 0x1c, 0x09, 0xfd, 0x5f, 0xb6, 0x5d, - 0xdb, 0x12, 0x46, 0xdd, 0x75, 0x9f, 0x31, 0xbe, 0x72, 0x95, 0x4b, 0xe7, 0x7d, 0x82, 0x51, 0x37, - 0x24, 0x12, 0xf9, 0x31, 0x17, 0x32, 0x6f, 0x07, 0x5d, 0x88, 0x75, 0x37, 0xb4, 0x4f, 0xff, 0xed, - 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x8d, 0x12, 0x77, 0x0f, 0x03, 0x00, 0x00, + // 497 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0xdd, 0x6a, 0xd4, 0x40, + 0x14, 0xc7, 0xc9, 0x7e, 0xef, 0xe9, 0xba, 0xd6, 0xe9, 0x5a, 0xc6, 0x5c, 0x95, 0x40, 0xa5, 0x37, + 0xe6, 0xa2, 0x22, 0x2c, 0xbd, 0xd1, 0xb6, 0xac, 0x82, 0x50, 0x28, 0xb1, 0xf5, 0x56, 0xa6, 0xc9, + 0x71, 0x1d, 0x4c, 0x32, 0x21, 0x33, 0x2d, 0xe4, 0x31, 0x7c, 0x04, 0xdf, 0x40, 0xf0, 0x05, 0x65, + 0x3e, 0xb2, 0x9b, 0xac, 0xbb, 0xed, 0x5d, 0x7e, 0xe7, 0x8b, 0x39, 0xe7, 0xff, 0x27, 0xf0, 0x42, + 0x62, 0x9e, 0x7c, 0x93, 0x58, 0x3e, 0x60, 0x19, 0x16, 0xa5, 0x50, 0x82, 0xf4, 0x58, 0xc1, 0x65, + 0xf0, 0xc7, 0x03, 0xf8, 0x82, 0x79, 0x72, 0xcd, 0x4a, 0x96, 0x49, 0x42, 0x61, 0x78, 0x29, 0x72, + 0xc5, 0x62, 0x45, 0xbd, 0x23, 0xef, 0x64, 0x1c, 0xd5, 0x48, 0x66, 0xd0, 0xbf, 0x11, 0x05, 0x8f, + 0x69, 0xc7, 0xc4, 0x2d, 0x98, 0x28, 0x57, 0x29, 0xd2, 0xae, 0x8b, 0x6a, 0xd0, 0x53, 0xae, 0x50, + 0x4a, 0xb6, 0x44, 0xda, 0xb3, 0x53, 0x1c, 0x12, 0x1f, 0x46, 0xd7, 0x25, 0x17, 0x25, 0x57, 0x15, + 0xed, 0x9b, 0xd4, 0x8a, 0xc9, 0x6b, 0x98, 0x46, 0x98, 0x09, 0x85, 0x37, 0x98, 0x15, 0x29, 0x53, + 0x48, 0x07, 0xa6, 0x62, 0x23, 0x1a, 0xfc, 0xf2, 0x80, 0xdc, 0x16, 0x09, 0x53, 0x78, 0x29, 0xf2, + 0xef, 0x7c, 0xe9, 0x9e, 0xfe, 0x1e, 0x86, 0xb1, 0x61, 0x49, 0xbd, 0xa3, 0xee, 0xc9, 0xde, 0xe9, + 0x71, 0xa8, 0x37, 0x0c, 0xff, 0x2f, 0x0d, 0x2d, 0xc8, 0x45, 0xae, 0xca, 0x2a, 0xaa, 0xbb, 0xfc, + 0x33, 0x98, 0x34, 0x13, 0x64, 0x1f, 0xba, 0x3f, 0xb1, 0x72, 0x77, 0xd0, 0x9f, 0x7a, 0xdb, 0x07, + 0x96, 0xde, 0x63, 0x7d, 0x03, 0x03, 0x67, 0x9d, 0xb9, 0x17, 0x84, 0x30, 0xbb, 0x95, 0x58, 0xf2, + 0xe4, 0xa2, 0xba, 0x12, 0x77, 0x3c, 0x45, 0xf7, 0xa8, 0x43, 0x18, 0x64, 0x86, 0xdd, 0x18, 0x47, + 0xc1, 0x10, 0xfa, 0x8b, 0xac, 0x50, 0x55, 0xf0, 0x06, 0x0e, 0xda, 0x8d, 0x11, 0x16, 0x69, 0xa5, + 0xfb, 0xee, 0x4d, 0xb8, 0xee, 0xb3, 0x14, 0x9c, 0xc3, 0xc1, 0x57, 0x96, 0xf2, 0xf5, 0x46, 0xb6, + 0x9c, 0xc2, 0x90, 0x4b, 0x93, 0x30, 0xf5, 0xa3, 0xa8, 0x46, 0xbd, 0x44, 0x26, 0x97, 0xee, 0xc1, + 0xfa, 0x33, 0xf8, 0xed, 0xc1, 0xf3, 0x0b, 0xa6, 0xe2, 0x1f, 0x0d, 0xd9, 0x7d, 0x18, 0x39, 0x9d, + 0xed, 0xf1, 0xc6, 0xd1, 0x8a, 0xd7, 0x12, 0x77, 0x76, 0x48, 0xdc, 0xdd, 0x2d, 0x71, 0xef, 0x49, + 0x89, 0xfb, 0x5b, 0x25, 0xfe, 0x00, 0x93, 0x8f, 0x8c, 0xa7, 0x98, 0x44, 0x18, 0x8b, 0x32, 0x79, + 0xc4, 0x96, 0x87, 0x30, 0x88, 0x90, 0x49, 0x91, 0xbb, 0xe7, 0x39, 0x0a, 0x3e, 0xc3, 0x74, 0xb5, + 0xa4, 0xbd, 0xd1, 0x1c, 0x9e, 0x35, 0x67, 0xd6, 0x2e, 0x21, 0xd6, 0x25, 0xcd, 0x54, 0xd4, 0x2e, + 0x3c, 0xfd, 0xdb, 0x81, 0xb1, 0x9e, 0x73, 0xbe, 0xc4, 0x5c, 0x91, 0x63, 0xe8, 0x69, 0x20, 0xfb, + 0xb6, 0x71, 0x7d, 0x45, 0x7f, 0xcf, 0x46, 0x8c, 0xb0, 0xe4, 0x1d, 0x4c, 0x9a, 0xce, 0x23, 0x74, + 0x97, 0x1b, 0xdb, 0x6d, 0x0b, 0x98, 0xb6, 0x05, 0x7e, 0xa4, 0xf1, 0x95, 0xcd, 0x6c, 0x33, 0xc4, + 0x27, 0x98, 0xb6, 0x6d, 0x45, 0x7c, 0x37, 0x66, 0x8b, 0x4b, 0xeb, 0x41, 0xdb, 0x8c, 0x38, 0x87, + 0xf1, 0xea, 0x8e, 0xe4, 0xa5, 0xad, 0xdb, 0x70, 0x8f, 0x3f, 0xdb, 0x08, 0x9b, 0xce, 0xbb, 0x81, + 0xf9, 0xcd, 0xbc, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0x71, 0x6f, 0x12, 0x61, 0x7b, 0x04, 0x00, + 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -370,6 +523,7 @@ type SendAgentClient interface { UpdateConfig(ctx context.Context, in *UpdateConfigParams, opts ...grpc.CallOption) (*Empty, error) ValidateConfig(ctx context.Context, in *UpdateConfigParams, opts ...grpc.CallOption) (*ValidateConfigReply, error) UseridByMobile(ctx context.Context, in *UseridByMobileParams, opts ...grpc.CallOption) (*UseridByMobileReply, error) + BatchSend(ctx context.Context, in *BatchSendParams, opts ...grpc.CallOption) (*BatchSendReply, error) } type sendAgentClient struct { @@ -416,12 +570,22 @@ func (c *sendAgentClient) UseridByMobile(ctx context.Context, in *UseridByMobile return out, nil } +func (c *sendAgentClient) BatchSend(ctx context.Context, in *BatchSendParams, opts ...grpc.CallOption) (*BatchSendReply, error) { + out := new(BatchSendReply) + err := c.cc.Invoke(ctx, "/apis.SendAgent/BatchSend", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // SendAgentServer is the server API for SendAgent service. type SendAgentServer interface { Send(context.Context, *SendParams) (*Empty, error) UpdateConfig(context.Context, *UpdateConfigParams) (*Empty, error) ValidateConfig(context.Context, *UpdateConfigParams) (*ValidateConfigReply, error) UseridByMobile(context.Context, *UseridByMobileParams) (*UseridByMobileReply, error) + BatchSend(context.Context, *BatchSendParams) (*BatchSendReply, error) } // UnimplementedSendAgentServer can be embedded to have forward compatible implementations. @@ -440,6 +604,9 @@ func (*UnimplementedSendAgentServer) ValidateConfig(ctx context.Context, req *Up func (*UnimplementedSendAgentServer) UseridByMobile(ctx context.Context, req *UseridByMobileParams) (*UseridByMobileReply, error) { return nil, status.Errorf(codes.Unimplemented, "method UseridByMobile not implemented") } +func (*UnimplementedSendAgentServer) BatchSend(ctx context.Context, req *BatchSendParams) (*BatchSendReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method BatchSend not implemented") +} func RegisterSendAgentServer(s *grpc.Server, srv SendAgentServer) { s.RegisterService(&_SendAgent_serviceDesc, srv) @@ -517,6 +684,24 @@ func _SendAgent_UseridByMobile_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _SendAgent_BatchSend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchSendParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SendAgentServer).BatchSend(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/apis.SendAgent/BatchSend", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SendAgentServer).BatchSend(ctx, req.(*BatchSendParams)) + } + return interceptor(ctx, in, info, handler) +} + var _SendAgent_serviceDesc = grpc.ServiceDesc{ ServiceName: "apis.SendAgent", HandlerType: (*SendAgentServer)(nil), @@ -537,6 +722,10 @@ var _SendAgent_serviceDesc = grpc.ServiceDesc{ MethodName: "UseridByMobile", Handler: _SendAgent_UseridByMobile_Handler, }, + { + MethodName: "BatchSend", + Handler: _SendAgent_BatchSend_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "send_server.proto", diff --git a/pkg/notify/rpc/apis/send_server.proto b/pkg/notify/rpc/apis/send_server.proto index 3652b668ca..d5bc98880c 100644 --- a/pkg/notify/rpc/apis/send_server.proto +++ b/pkg/notify/rpc/apis/send_server.proto @@ -45,9 +45,27 @@ message ValidateConfigReply { string msg = 2; } +message BatchSendParams { + repeated string Contacts = 1; + string Title = 2; + string Message = 3; + string Priority = 4; + string RemoteTemplate = 5; +} + +message FailedRecord { + string Contact = 1; + string Reason = 2; +} + +message BatchSendReply { + repeated FailedRecord FailedRecords = 1; +} + service SendAgent { rpc Send(SendParams) returns (Empty); rpc UpdateConfig(UpdateConfigParams) returns (Empty); rpc ValidateConfig(UpdateConfigParams) returns (ValidateConfigReply); rpc UseridByMobile(UseridByMobileParams) returns (UseridByMobileReply); + rpc BatchSend (BatchSendParams) returns (BatchSendReply); } diff --git a/pkg/notify/rpc/send.go b/pkg/notify/rpc/send.go index 23bfea9dca..6d5240e4d1 100644 --- a/pkg/notify/rpc/send.go +++ b/pkg/notify/rpc/send.go @@ -32,7 +32,7 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/mcclient" - _interface "yunion.io/x/onecloud/pkg/notify/interface" + notifyv2 "yunion.io/x/onecloud/pkg/notify" "yunion.io/x/onecloud/pkg/notify/models" "yunion.io/x/onecloud/pkg/notify/rpc/apis" "yunion.io/x/onecloud/pkg/util/fileutils2" @@ -49,13 +49,13 @@ const ( type SRpcService struct { SendServices *ServiceMap socketFileDir string - configStore _interface.IServiceConfigStore - templateStore _interface.ITemplateStore + configStore notifyv2.IServiceConfigStore + templateStore notifyv2.ITemplateStore } // NewSRpcService create a SRpcService -func NewSRpcService(socketFileDir string, configStore _interface.IServiceConfigStore, - tempalteStore _interface.ITemplateStore) *SRpcService { +func NewSRpcService(socketFileDir string, configStore notifyv2.IServiceConfigStore, + tempalteStore notifyv2.ITemplateStore) *SRpcService { return &SRpcService{ SendServices: NewServiceMap(), socketFileDir: socketFileDir, @@ -129,10 +129,36 @@ func (self *SRpcService) Send(ctx context.Context, contactType, contact, topic, return nil } +func (self *SRpcService) BatchSend(ctx context.Context, contacts []string, contactType, topic, message, priority string) ([]*apis.FailedRecord, error) { + args, err := self.templateStore.NotifyFilter(contactType, topic, message) + if err != nil { + return nil, errors.Wrap(err, "templateStore.NotifyFilter") + } + + batchSendParams := apis.BatchSendParams{ + Contacts: contacts, + Title: args.Title, + Message: args.Message, + Priority: args.Priority, + RemoteTemplate: args.RemoteTemplate, + } + + f := func(service *apis.SendNotificationClient) (interface{}, error) { + return service.BatchSend(ctx, &batchSendParams) + } + + ret, err := self.execute(ctx, f, contactType) + if err != nil { + return nil, errors.Wrapf(err, "contactType '%s'", contactType) + } + reply := ret.(*apis.BatchSendReply) + return reply.FailedRecords, nil +} + // RestartService can restart remote rpc server and pass config info. // This function should be call immediately after init notify server firstly // This function should be call immediately after accept the request about changing config. -func (self *SRpcService) RestartService(ctx context.Context, config _interface.SConfig, serviceName string) { +func (self *SRpcService) RestartService(ctx context.Context, config notifyv2.SConfig, serviceName string) { _, err := self.restartWithConfig(ctx, serviceName, config) if err != nil { log.Debugf("restart service failed: %s", err) diff --git a/pkg/notify/cache/doc.go b/pkg/notify/service/doc.go similarity index 89% rename from pkg/notify/cache/doc.go rename to pkg/notify/service/doc.go index 17645129dc..b10d2f1886 100644 --- a/pkg/notify/cache/doc.go +++ b/pkg/notify/service/doc.go @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cache // import "yunion.io/x/onecloud/pkg/notify/cache" +package service // import "yunion.io/x/onecloud/pkg/notify/service" diff --git a/pkg/notify/service/handlers.go b/pkg/notify/service/handlers.go new file mode 100644 index 0000000000..0a296ef079 --- /dev/null +++ b/pkg/notify/service/handlers.go @@ -0,0 +1,63 @@ +package service + +import ( + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/appsrv/dispatcher" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/notify/models" + "yunion.io/x/onecloud/pkg/notify/oldmodels" +) + +const ( + API_VERSION = "v2" +) + +func InitHandlers(app *appsrv.Application) { + db.InitAllManagers() + + db.RegistUserCredCacheUpdater() + + db.AddScopeResourceCountHandler(API_VERSION, app) + + // Data migration + db.RegisterModelManager(oldmodels.NotificationManager) + db.RegisterModelManager(oldmodels.ContactManager) + db.RegisterModelManager(oldmodels.ConfigManager) + db.RegisterModelManager(oldmodels.TemplateManager) + db.RegisterModelManager(oldmodels.UserCacheManager) + + taskman.AddTaskHandler(API_VERSION, app) + for _, manager := range []db.IModelManager{ + taskman.TaskManager, + taskman.SubTaskManager, + taskman.TaskObjectManager, + + db.UserCacheManager, + db.TenantCacheManager, + models.SubContactManager, + models.VerificationManager, + } { + db.RegisterModelManager(manager) + } + for _, manager := range []db.IModelManager{ + db.OpsLog, + db.Metadata, + + models.ReceiverManager, + models.NotificationManager, + models.ConfigManager, + models.TemplateManager, + } { + db.RegisterModelManager(manager) + handler := db.NewModelHandler(manager) + dispatcher.AddModelDispatcher(API_VERSION, app, handler) + } + for _, manager := range []db.IJointModelManager{ + models.ReceiverNotificationManager, + } { + db.RegisterModelManager(manager) + handler := db.NewJointModelHandler(manager) + dispatcher.AddJointModelDispatcher(API_VERSION, app, handler) + } +} diff --git a/pkg/notify/service.go b/pkg/notify/service/service.go similarity index 88% rename from pkg/notify/service.go rename to pkg/notify/service/service.go index b74411c70a..d2b6cf9a20 100644 --- a/pkg/notify/service.go +++ b/pkg/notify/service/service.go @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package notify +package service import ( - "context" "os" "time" @@ -29,12 +28,11 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/cronman" "yunion.io/x/onecloud/pkg/cloudcommon/db" common_options "yunion.io/x/onecloud/pkg/cloudcommon/options" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/notify/cache" "yunion.io/x/onecloud/pkg/notify/models" "yunion.io/x/onecloud/pkg/notify/options" _ "yunion.io/x/onecloud/pkg/notify/policy" "yunion.io/x/onecloud/pkg/notify/rpc" + _ "yunion.io/x/onecloud/pkg/notify/tasks" ) func StartService() { @@ -60,9 +58,6 @@ func StartService() { db.EnsureAppInitSyncDB(applicaion, dbOpts, models.InitDB) defer cloudcommon.CloseDB() - // init cache - cache.RegistUserCredCacheUpdater() - // init notify service models.NotifyService = rpc.NewSRpcService(opts.SocketFileDir, models.ConfigManager, models.TemplateManager) models.NotifyService.InitAll() @@ -73,10 +68,7 @@ func StartService() { cron.AddJobAtIntervals("UpdateServices", time.Duration(opts.UpdateInterval)*time.Minute, models.NotifyService.UpdateServices) // wrapped func to resend notifications - resend := func(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { - models.ReSend(opts.ReSendScope) - } - cron.AddJobAtIntervals("ReSendNotifications", time.Duration(opts.ReSendScope)*time.Second, resend) + cron.AddJobAtIntervals("ReSendNotifications", time.Duration(opts.ReSendScope)*time.Second, models.NotificationManager.ReSend) cron.Start() app.ServeForever(applicaion, baseOpts) diff --git a/pkg/notify/utils/doc.go b/pkg/notify/tasks/doc.go similarity index 90% rename from pkg/notify/utils/doc.go rename to pkg/notify/tasks/doc.go index 37fa0628ed..4a54e435f1 100644 --- a/pkg/notify/utils/doc.go +++ b/pkg/notify/tasks/doc.go @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package utils // import "yunion.io/x/onecloud/pkg/notify/utils" +package tasks // import "yunion.io/x/onecloud/pkg/notify/tasks" diff --git a/pkg/notify/tasks/notifications_send_task.go b/pkg/notify/tasks/notifications_send_task.go new file mode 100644 index 0000000000..ad313e8ee9 --- /dev/null +++ b/pkg/notify/tasks/notifications_send_task.go @@ -0,0 +1,148 @@ +package tasks + +import ( + "context" + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + apis "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/notify/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type NotificationSendTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(NotificationSendTask{}) +} + +func (self *NotificationSendTask) taskFailed(ctx context.Context, notification *models.SNotification, reason string, all bool) { + log.Errorf("fail to send notification %q", notification.GetId()) + if all { + notification.SetStatus(self.UserCred, apis.NOTIFICATION_STATUS_FAILED, reason) + } else { + notification.SetStatus(self.UserCred, apis.NOTIFICATION_STATUS_PART_OK, reason) + } + notification.AddOne() + logclient.AddActionLogWithContext(ctx, notification, logclient.ACT_SEND_NOTIFICATION, reason, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(reason)) +} + +func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + notification := obj.(*models.SNotification) + if notification.Status == apis.NOTIFICATION_STATUS_OK { + self.SetStageComplete(ctx, nil) + return + } + rns, err := notification.ReceiverNotificationsNotOK() + if err != nil { + self.taskFailed(ctx, notification, "fail to fetch ReceiverNotifications", true) + return + } + notification.SetStatus(self.UserCred, apis.NOTIFICATION_STATUS_SENDING, "") + + // sort out what needs to be sent + failedRecord := make([]string, 0) + sendFail := func(rn *models.SReceiverNotification, reason string) { + rn.AfterSend(ctx, false, reason) + failedRecord = append(failedRecord, fmt.Sprintf("%s: %s", rn.ReceiverID, reason)) + } + + // build contactMap + contactMap := make(map[string]*models.SReceiverNotification) + for i := range rns { + if len(rns[i].ReceiverID) == 0 { + contactMap[rns[i].Contact] = &rns[i] + continue + } + receiver, err := rns[i].Receiver() + if err != nil { + sendFail(&rns[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")) + 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())) + continue + } + if !enabled { + sendFail(&rns[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())) + continue + } + if !verified { + sendFail(&rns[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) + continue + } + contactMap[contact] = &rns[i] + } + + if len(contactMap) == 0 { + self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), true) + } + + // set status before send + now := time.Now() + contacts := make([]string, 0, len(contactMap)) + for c, rn := range contactMap { + rn.BeforeSend(ctx, now) + contacts = append(contacts, c) + } + + // send + ret, err := models.NotifyService.BatchSend(ctx, contacts, notification.ContactType, notification.Topic, notification.Message, notification.Priority) + if err != nil { + for _, rn := range contactMap { + rn.AfterSend(ctx, false, err.Error()) + } + failedRecord = append(failedRecord, fmt.Sprintf("others: %s", err.Error())) + self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), true) + return + } + + // check result + for _, fd := range ret { + rn := contactMap[fd.Contact] + rn.AfterSend(ctx, false, fd.Reason) + failedRecord = append(failedRecord, fmt.Sprintf("%s: %s", rn.ReceiverID, fd.Reason)) + } + if len(failedRecord) == len(contacts) { + self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), true) + return + } + if len(failedRecord) > 0 { + self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), false) + return + } + log.Infof("successfully send notification %q", notification.GetId()) + notification.SetStatus(self.UserCred, apis.NOTIFICATION_STATUS_OK, "") + logclient.AddActionLogWithContext(ctx, notification, logclient.ACT_SEND_NOTIFICATION, "", self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/notify/tasks/subcontact_pull_task.go b/pkg/notify/tasks/subcontact_pull_task.go new file mode 100644 index 0000000000..e209a17006 --- /dev/null +++ b/pkg/notify/tasks/subcontact_pull_task.go @@ -0,0 +1,87 @@ +// 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 tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + apis "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/notify/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +var PullContactType = []string{ + apis.DINGTALK, + apis.FEISHU, + apis.WORKWX, +} + +type SubcontactPullTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SubcontactPullTask{}) +} + +func (self *SubcontactPullTask) taskFailed(ctx context.Context, receiver *models.SReceiver, reason string) { + log.Errorf("fail to pull subcontact of receiver %q: %s", receiver.Id, reason) + receiver.SetStatus(self.UserCred, apis.RECEIVER_STATUS_PULL_FAILED, reason) + logclient.AddActionLogWithContext(ctx, receiver, logclient.ACT_PULL_SUBCONTACT, reason, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(reason)) +} + +func (self *SubcontactPullTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + // pull contacts + receiver := obj.(*models.SReceiver) + if len(receiver.Mobile) == 0 { + self.SetStageComplete(ctx, nil) + return + } + enabledContactTypes, _ := receiver.GetEnabledContactTypes() + for _, cType := range enabledContactTypes { + if !utils.IsInStringArray(cType, PullContactType) { + continue + } + userid, err := models.NotifyService.ContactByMobile(ctx, receiver.Mobile, cType) + if err != nil { + reason := fmt.Sprintf("fail to get %s contact by mobile %q: %v", cType, receiver.Mobile, err) + self.taskFailed(ctx, receiver, reason) + return + } + receiver.SetContact(cType, userid) + receiver.MarkContactTypeVerified(cType) + } + receiver.SetContact(apis.WEBCONSOLE, receiver.Id) + receiver.MarkContactTypeVerified(apis.WEBCONSOLE) + // push cache + err := receiver.PushCache(ctx) + if err != nil { + reason := fmt.Sprintf("PushCache: %v", err) + self.taskFailed(ctx, receiver, reason) + return + } + // success + receiver.SetStatus(self.UserCred, apis.RECEIVER_STATUS_READY, "") + logclient.AddActionLogWithContext(ctx, receiver, logclient.ACT_PULL_SUBCONTACT, "", self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/notify/tasks/verification_send_task.go b/pkg/notify/tasks/verification_send_task.go new file mode 100644 index 0000000000..5b7e52ffce --- /dev/null +++ b/pkg/notify/tasks/verification_send_task.go @@ -0,0 +1,76 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + api "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/notify/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type VerificationSendTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(VerificationSendTask{}) +} + +func (self *VerificationSendTask) taskFailed(ctx context.Context, receiver *models.SReceiver, reason string) { + log.Errorf("fail to send verification: %s", reason) + logclient.AddActionLogWithContext(ctx, receiver, logclient.ACT_SEND_VERIFICATION, reason, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(reason)) +} + +func (self *VerificationSendTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + receiver := obj.(*models.SReceiver) + contactType, _ := self.Params.GetString("contact_type") + verification, err := models.VerificationManager.Get(receiver.GetId(), contactType) + if err != nil { + self.taskFailed(ctx, receiver, fmt.Sprintf("VerificationManager.Get for receiver_id %q and contact_type %q: %s", receiver.GetId(), contactType, err.Error())) + return + } + contact, err := receiver.GetContact(contactType) + if err != nil { + self.taskFailed(ctx, receiver, fmt.Sprintf("fail to get contact(type: %s): %s", contactType, err.Error())) + return + } + + // build message + var message string + switch contactType { + case api.EMAIL: + info, err := models.TemplateManager.GetCompanyInfo(ctx) + if err != nil { + self.taskFailed(ctx, receiver, fmt.Sprintf("fail to get company info: %s", err.Error())) + return + } + data := struct { + models.SCompanyInfo + Name string + Code string + }{ + Name: receiver.Name, + Code: verification.Token, + SCompanyInfo: info, + } + message = jsonutils.Marshal(data).String() + case api.MOBILE: + message = fmt.Sprintf(`{"code": "%s"}`, verification.Token) + default: + // no way + } + err = models.NotifyService.Send(ctx, contactType, contact, "verify", message, "") + if err != nil { + self.taskFailed(ctx, receiver, err.Error()) + return + } + logclient.AddActionLogWithContext(ctx, receiver, logclient.ACT_SEND_VERIFICATION, "", self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/notify/utils/keystone.go b/pkg/notify/utils/keystone.go deleted file mode 100644 index e512b298ec..0000000000 --- a/pkg/notify/utils/keystone.go +++ /dev/null @@ -1,81 +0,0 @@ -// 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 utils - -import ( - "context" - - "yunion.io/x/pkg/errors" - "yunion.io/x/sqlchemy" - - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/notify/cache" -) - -func GetUserByIDOrName(ctx context.Context, idStr string) (*cache.SUser, error) { - return cache.UserCacheManager.FetchUserByIDOrName(ctx, idStr) -} - -func GetUsersWithoutRemote(ctx context.Context, idStr []string) ([]cache.SUser, error) { - q := cache.UserCacheManager.Query() - q = q.Filter(sqlchemy.OR(sqlchemy.In(q.Field("id"), idStr), sqlchemy.In(q.Field("name"), idStr))).Desc("updated_at") - return cache.UserCacheManager.FetchUserFromLoaclCache(ctx, q) -} - -func DeleteUsers(ctx context.Context, userCred mcclient.TokenCredential, ids []string) error { - users, err := GetUsersWithoutRemote(ctx, ids) - if err != nil { - return err - } - for i := range users { - err := users[i].Delete(ctx, userCred) - if err != nil { - return errors.Wrapf(err, "delete cache.SUser %s error", users[i].Id) - } - } - return nil -} - -func GetUserIdsLikeName(ctx context.Context, name string) ([]string, error) { - users, err := cache.UserCacheManager.FetchUserLikeName(ctx, name, true) - if err != nil { - return nil, err - } - ret := make([]string, len(users)) - for i := range users { - ret[i] = users[i].Id - } - return ret, nil -} - -func GetUsersByGroupID(ctx context.Context, gid string) ([]string, error) { - ret, err := cache.UserGroupCacheManager.FetchByGroupId(ctx, gid) - if err != nil { - return nil, err - } - ids := make([]string, len(ret)) - for i := range ret { - ids[i] = ret[i].UserId - } - return ids, nil -} - -func GetUsernameByID(ctx context.Context, id string) (string, error) { - user, err := GetUserByIDOrName(ctx, id) - if err != nil { - return "", err - } - return user.Name, nil -} diff --git a/pkg/notify/utils/others.go b/pkg/notify/utils/others.go deleted file mode 100644 index db07d4448b..0000000000 --- a/pkg/notify/utils/others.go +++ /dev/null @@ -1,68 +0,0 @@ -// 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 utils - -import ( - "bytes" - "context" - "fmt" - "math/rand" - "time" - - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/util/rbacutils" -) - -func FetchOwnerId(ctx context.Context, manager db.IModelManager, userCred mcclient.TokenCredential, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) { - var ownerId mcclient.IIdentityProvider - var err error - if manager.ResourceScope() != rbacutils.ScopeSystem { - ownerId, err = manager.FetchOwnerId(ctx, data) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - } - if ownerId == nil { - ownerId = userCred - } - return ownerId, nil -} - -func GenerateMobileToken() string { - rnd := rand.New(rand.NewSource(time.Now().UnixNano())) - token := fmt.Sprintf("%06v", rnd.Int31n(1000000)) - return token -} - -func GenerateEmailToken(tokenLen int) string { - rnd := rand.New(rand.NewSource(time.Now().UnixNano())) - token := new(bytes.Buffer) - for token.Len() < tokenLen { - token.WriteString(fmt.Sprintf("%x", rnd.Int31())) - } - return token.String()[:tokenLen] -} - -func JsonArrayToStringArray(src []jsonutils.JSONObject) []string { - des := make([]string, len(src)) - for i := range src { - des[i], _ = src[i].GetString() - } - return des -} diff --git a/pkg/notify/utils/utils_test.go b/pkg/notify/utils/utils_test.go deleted file mode 100644 index 08eff8c86c..0000000000 --- a/pkg/notify/utils/utils_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// 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 utils - -import ( - "testing" -) - -func TestGenEmailToken(t *testing.T) { - s := GenerateEmailToken(32) - if len(s) != 32 { - t.Error("email token length should be 32") - } -} - -func TestGenMobileToke(t *testing.T) { - s := GenerateMobileToken() - if len(s) != 6 { - t.Errorf("mobile token length should be 6") - } -} diff --git a/pkg/util/logclient/consts.go b/pkg/util/logclient/consts.go index 1acdeab728..1678e749a4 100644 --- a/pkg/util/logclient/consts.go +++ b/pkg/util/logclient/consts.go @@ -205,4 +205,8 @@ const ( ACT_UPDATE_BILLING_OPTIONS = "更新账单文件" ACT_UPDATE_CREDENTIAL = "更新账号密码" + + ACT_PULL_SUBCONTACT = "拉取联系方式" + ACT_SEND_NOTIFICATION = "发送通知消息" + ACT_SEND_VERIFICATION = "发送验证消息" ) diff --git a/scripts/codegen.py b/scripts/codegen.py index 091a7c8877..bbf849f1f9 100755 --- a/scripts/codegen.py +++ b/scripts/codegen.py @@ -56,6 +56,8 @@ def run_swagger_yaml(svc, swagger_pkg_dir, output_dir): cmd.extend(["-o", pjoin(output_dir, "swagger_%s.yaml" % svc)]) run_cmd(cmd) +def remove_prefix(text, prefix): + return text[text.startswith(prefix) and len(prefix):] class FuncDispatcher(object): @@ -71,7 +73,7 @@ class FuncDispatcher(object): func = getattr(self, attr) if not callable(func): continue - svc = attr.lstrip('gen_') + svc = remove_prefix(attr, 'gen_') gen_dict[svc] = func self.gen_dict = gen_dict @@ -131,6 +133,7 @@ class ModelAPI(FuncDispatcher): self.run_model("compute") self.run_model("image") self.run_model("cloudid") + self.run_model("notify") def gen_monitor(self): self.run(pkg=["monitor", "models"], out=["monitor"]) @@ -174,6 +177,8 @@ class SwaggerCode(FuncDispatcher): def gen_monitor(self): self.run("monitor", pkg=["models"], out="monitor") + def gen_notify(self): + self.run("notify", pkg=["models"], out="notify") class SwaggerYAML(FuncDispatcher): @@ -204,6 +209,8 @@ class SwaggerYAML(FuncDispatcher): def gen_monitor(self): self.run("monitor") + def gen_notify(self): + self.run("notify") class SwaggerServe(object):