feat(notify): topic and subscriber at the domain level

This commit is contained in:
rainzm
2021-06-24 10:29:35 +08:00
parent 1d1bb6c381
commit 04d930ab2b
17 changed files with 1256 additions and 1039 deletions
+11 -6
View File
@@ -21,10 +21,15 @@ import (
)
func init() {
cmd := shell.NewResourceCmd(&modules.NotifySubscription).WithKeyword("notify-subscription")
cmd.List(new(notify.SubscriptionListOptions))
cmd.Show(new(notify.SubscriptionOptions))
cmd.Perform("set-receiver", new(notify.SubscriptionSetReceiverOptions))
cmd.Perform("set-robot", new(notify.SubscriptionSetRobotOptions))
cmd.Perform("set-webhook", new(notify.SubscriptionSetWebhookOptions))
cmd := shell.NewResourceCmd(&modules.NotifyTopic).WithKeyword("notify-topic")
cmd.List(new(notify.TopicListOptions))
cmd1 := shell.NewResourceCmd(&modules.NotifySubscriber).WithKeyword("notify-subscriber")
cmd1.List(new(notify.SubscriberListOptions))
cmd1.Create(new(notify.SubscriberCreateOptions))
cmd1.Show(new(notify.SubscriberOptions))
cmd1.Delete(new(notify.SubscriberOptions))
cmd1.Perform("set-receiver", new(notify.SubscriberSetReceiverOptions))
cmd1.Perform("enable", new(notify.SubscriberOptions))
cmd1.Perform("disable", new(notify.SubscriberOptions))
}
+27 -25
View File
@@ -84,31 +84,33 @@ const (
RECEIVER_TYPE_CONTACT = "contact"
RECEIVER_TYPE_ROBOT = "robot"
SUBSCRIPTION_RESOURCE_CREATE_DELETE = "resource create delete"
SUBSCRIPTION_RESOURCE_CHANGECONFIG = "resource change config"
SUBSCRIPTION_RESOURCE_UPDATE = "resource udpate"
SUBSCRIPTION_RESOURCE_EXPIRED_RELEASE = "resource expired release"
SUBSCRIPTION_AUTOMATED_PROCESS_EXECUTION = "automated process execution"
TOPIC_TYPE_RESOURCE = "resource"
TOPIC_TYPE_AUTOMATED_PROCESS = "automated_process"
SUBSCRIPTION_TYPE_RESOURCE = "resource"
SUBSCRIPTION_TYPE_AUTOMATED_PROCESS = "automated_process"
TOPIC_RESOURCE_SERVER = "server"
TOPIC_RESOURCE_SCALINGGROUP = "scalinggroup"
TOPIC_RESOURCE_SCALINGPOLICY = "scalingpolicy"
TOPIC_RESOURCE_IMAGE = "image"
TOPIC_RESOURCE_DISK = "disk"
TOPIC_RESOURCE_SNAPSHOT = "snapshot"
TOPIC_RESOURCE_INSTANCESNAPSHOT = "instance_snapshot"
TOPIC_RESOURCE_SNAPSHOTPOLICY = "snapshotpolicy"
TOPIC_RESOURCE_NETWORK = "network"
TOPIC_RESOURCE_EIP = "eip"
TOPIC_RESOURCE_SECGROUP = "secgroup"
TOPIC_RESOURCE_LOADBALANCER = "loadbalancer"
TOPIC_RESOURCE_LOADBALANCERACL = "loadbalanceracl"
TOPIC_RESOURCE_LOADBALANCERCERTIFICATE = "loadbalancercertificate"
TOPIC_RESOURCE_BUCKET = "bucket"
TOPIC_RESOURCE_DBINSTANCE = "dbinstance"
TOPIC_RESOURCE_ELASTICCACHE = "elasticcache"
TOPIC_RESOURCE_SCHEDULEDTASK = "scheduledtask"
SUBSCRIPTION_RESOURCE_SERVER = "server"
SUBSCRIPTION_RESOURCE_SCALINGGROUP = "scalinggroup"
SUBSCRIPTION_RESOURCE_SCALINGPOLICY = "scalingpolicy"
SUBSCRIPTION_RESOURCE_IMAGE = "image"
SUBSCRIPTION_RESOURCE_DISK = "disk"
SUBSCRIPTION_RESOURCE_SNAPSHOT = "snapshot"
SUBSCRIPTION_RESOURCE_INSTANCESNAPSHOT = "instance_snapshot"
SUBSCRIPTION_RESOURCE_SNAPSHOTPOLICY = "snapshotpolicy"
SUBSCRIPTION_RESOURCE_NETWORK = "network"
SUBSCRIPTION_RESOURCE_EIP = "eip"
SUBSCRIPTION_RESOURCE_SECGROUP = "secgroup"
SUBSCRIPTION_RESOURCE_LOADBALANCER = "loadbalancer"
SUBSCRIPTION_RESOURCE_LOADBALANCERACL = "loadbalanceracl"
SUBSCRIPTION_RESOURCE_LOADBALANCERCERTIFICATE = "loadbalancercertificate"
SUBSCRIPTION_RESOURCE_BUCKET = "bucket"
SUBSCRIPTION_RESOURCE_DBINSTANCE = "dbinstance"
SUBSCRIPTION_RESOURCE_ELASTICCACHE = "elasticcache"
SUBSCRIPTION_RESOURCE_SCHEDULEDTASK = "scheduledtask"
SUBSCRIBER_TYPE_ROLE = "role"
SUBSCRIBER_TYPE_ROBOT = "robot"
SUBSCRIBER_TYPE_RECEIVER = "receiver"
SUBSCRIBER_SCOPE_SYSTEM = "system"
SUBSCRIBER_SCOPE_DOMAIN = "domain"
SUBSCRIBER_SCOPE_PROJECT = "project"
)
+87
View File
@@ -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 notify
import "yunion.io/x/onecloud/pkg/apis"
type SubscriberCreateInput struct {
apis.VirtualResourceCreateInput
// description: Id of Topic
// required
TopicID string
// description: scope of resource
// enum: system,domain,project
ResourceScope string
// description: Type of subscriber
// enum: receiver,robot,role
Type string
// description: receivers which is required when the type is 'receiver' will Subscribe TopicID
Receivers []string
// description: Role(Id or Name) which is required when the type is 'role' will Subscribe TopicID
Role string
// description: The scope of role subscribers
// enum: system,domain,project
RoleScope string
// description: Robot(Id or Name) which is required when the type is 'robot' will Subscribe TopicID
Robot string
}
type SubscriberListInput struct {
apis.VirtualResourceListInput
apis.EnabledResourceBaseListInput
// description: topic id
TopicID string
// description: scope of resource
// enum: system,domain,project
ResourceScope string
// description: type
// enum: receiver,robot,role
Type string
}
type Identification struct {
// example: 036fed49483b412888a760c2bc995caa
ID string `json:"id"`
// example: test
Name string `json:"name"`
}
type SubscriberDetails struct {
apis.VirtualResourceDetails
SSubscriber
// description: receivers
Receivers []Identification
// description: role
Role Identification
// description: robot
Robot Identification
}
type SubscriberSetReceiverInput struct {
Receivers []string
}
-77
View File
@@ -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/onecloud/pkg/apis"
type SubscriptionListInput struct {
}
type SubscriptionDetails struct {
apis.StatusStandaloneResourceDetails
// description: resources managed
// example: ["server", "eip", "disk"]
Resources []string `json:"resource_types"`
// description: receivers of the message sent
Receivers SubscriptionReceiver `json:"receivers"`
// description: type of robot send message
// example: dingtalk_robot
Robot string `json:"robot"`
// description: webhook send message
// example: webhook
Webhook string `json:"webhook"`
}
type IDAndName struct {
// example: 036fed49483b412888a760c2bc995caa
ID string `json:"id"`
// example: test
Name string `json:"name"`
}
type ReceivingRoleIDAndName struct {
IDAndName
// description: scope of role
// enum: system,domain,project
Scope string `json:"scope"`
}
type SubscriptionReceiver struct {
Receivers []IDAndName `json:"receivers"`
ReceivingRoles []ReceivingRoleIDAndName `json:"roles"`
}
type SubscriptionSetReceiverInput struct {
ReceivingRoles []ReceivingRole `json:"roles"`
Receivers []string `json:"receivers"`
}
type ReceivingRole struct {
// description: id or name of role
Role string `json:"role"`
// description: scope of role
// enum: system,domain,project
Scope string `json:"scope"`
}
type SubscriptionSetRobotInput struct {
// description: robot
// enum: feishu-robot,dingtalk-robot,workwx-robot
Robot string
}
type SubscriptionSetWebhookInput struct {
Webhook string
}
+30
View File
@@ -0,0 +1,30 @@
// 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 TopicListInput struct {
apis.StandaloneResourceListInput
}
type TopicDetails struct {
apis.StandaloneResourceDetails
STopic
// description: resources managed
// example: ["server", "eip", "disk"]
Resources []string `json:"resource_types"`
}
+42 -22
View File
@@ -23,8 +23,10 @@ import (
// 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"`
apis.SDomainizedResourceBase
Type string `json:"type"`
Content interface{} `json:"content"`
Attribution string `json:"attribution"`
}
// SNotification is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SNotification.
@@ -69,11 +71,21 @@ type SReceiverNotification struct {
NotificationID string `json:"notification_id"`
// ignore if ReceiverID is not empty or default
Contact string `json:"contact"`
ReceiverType string `json:"receiver_type"`
SendBy string `json:"send_by"`
Status string `json:"status"`
FailedReason string `json:"failed_reason"`
}
// SRobot is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SRobot.
type SRobot struct {
apis.SSharableVirtualResourceBase
apis.SEnabledResourceBase
Type string `json:"type"`
Address string `json:"address"`
Lang string `json:"lang"`
}
// SSubContact is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubContact.
type SSubContact struct {
apis.SStandaloneResourceBase
@@ -87,33 +99,31 @@ type SSubContact struct {
VerifiedNote string `json:"verified_note"`
}
// SSubscription is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscription.
type SSubscription struct {
apis.SStandaloneResourceBase
// SSubscriber is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscriber.
type SSubscriber struct {
apis.SVirtualResourceBase
apis.SEnabledResourceBase
Type string `json:"type"`
Resources uint64 `json:"resources"`
Actions uint32 `json:"actions"`
AdvanceDays int `json:"advance_days"`
TopicID string `json:"topic_id"`
Type string `json:"type"`
Identification string `json:"identification"`
RoleScope string `json:"role_scope"`
ResourceScope string `json:"resource_scope"`
}
// SSubscriptionReceiver is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscriptionReceiver.
type SSubscriptionReceiver struct {
apis.SStandaloneResourceBase
SubscriptionID string `json:"subscription_id"`
// role id or receiver id or other and the value type is determined by the ReceiverType
Receiver string `json:"receiver"`
ReceiverType string `json:"receiver_type"`
RoleScope string `json:"role_scope"`
}
// SSubscriptionReceiverDis is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscriptionReceiverDis.
type SSubscriptionReceiverDis struct {
SSubscriptionReceiver
// SSubscriberDis is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscriberDis.
type SSubscriberDis struct {
SSubscriber
ReceiverName string `json:"receiver_name"`
RoleName string `json:"role_name"`
}
// SSubscriberReceiver is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscriberReceiver.
type SSubscriberReceiver struct {
apis.SJointResourceBase
SubscriberId string `json:"subscriber_id"`
ReceiverId string `json:"receiver_id"`
}
// STemplate is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.STemplate.
type STemplate struct {
apis.SStandaloneResourceBase
@@ -126,6 +136,16 @@ type STemplate struct {
Example string `json:"example"`
}
// STopic is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.STopic.
type STopic struct {
apis.SStandaloneResourceBase
apis.SEnabledResourceBase
Type string `json:"type"`
Resources uint64 `json:"resources"`
Actions uint32 `json:"actions"`
AdvanceDays int `json:"advance_days"`
}
// SVerification is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SVerification.
type SVerification struct {
apis.SStandaloneResourceBase
+21 -12
View File
@@ -21,13 +21,14 @@ type ConfigsManager struct {
}
var (
NotifyReceiver modulebase.ResourceManager
NotifyConfig modulebase.ResourceManager
NotifyRobot modulebase.ResourceManager
Notification modulebase.ResourceManager
NotifyTemplate modulebase.ResourceManager
NotifySubscription modulebase.ResourceManager
Configs ConfigsManager
NotifyReceiver modulebase.ResourceManager
NotifyConfig modulebase.ResourceManager
NotifyRobot modulebase.ResourceManager
Notification modulebase.ResourceManager
NotifyTemplate modulebase.ResourceManager
NotifyTopic modulebase.ResourceManager
NotifySubscriber modulebase.ResourceManager
Configs ConfigsManager
)
func init() {
@@ -71,11 +72,19 @@ func init() {
)
register(&NotifyTemplate)
NotifySubscription = NewNotifyv2Manager(
"subscription",
"subscriptions",
[]string{"ID", "Name", "Type", "Resource_Types", "Receivers", "Robot", "Webhook"},
NotifyTopic = NewNotifyv2Manager(
"topic",
"topics",
[]string{"ID", "Name", "Type", "Enabled", "Resources"},
[]string{},
)
register(&NotifySubscription)
register(&NotifyTopic)
NotifySubscriber = NewNotifyv2Manager(
"subscriber",
"subscribers",
[]string{"ID", "Name", "Topic_Id", "Type", "Resource_Scope", "Role_Scope", "Receivers", "Role", "Robot"},
[]string{},
)
register(&NotifySubscriber)
}
+39 -47
View File
@@ -14,84 +14,76 @@
package notify
import (
"fmt"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
type SubscriptionListOptions struct {
type TopicListOptions struct {
options.BaseListOptions
}
func (opts *SubscriptionListOptions) Params() (jsonutils.JSONObject, error) {
func (opts *TopicListOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(opts)
}
type SubscriptionOptions struct {
ID string `help:"Id or Name of subscription"`
type TopicOptions struct {
ID string `help:"Id or Name of topic"`
}
func (so *SubscriptionOptions) GetId() string {
func (so *TopicOptions) GetId() string {
return so.ID
}
func (so *SubscriptionOptions) Params() (jsonutils.JSONObject, error) {
func (so *TopicOptions) Params() (jsonutils.JSONObject, error) {
return nil, nil
}
type SubscriptionSetReceiverOptions struct {
SubscriptionOptions
Receivers []string `json:"receivers"`
RoleAndScopes []string `json:"role_and_scopes" help:"role and scope, separated by a colon, example: admin:system"`
type SubscriberCreateOptions struct {
Name string `positional:"true"`
TopicId string `positional:"true"`
ResourceScope string `positional:"true" choices:"system|domain|project"`
Type string `positional:"true" choices:"receiver|robot|role"`
Receivers []string `help:"required if type is 'receiver'"`
Role string `help:"required if type is 'role'"`
RoleScope string `help:"required if type if 'role'"`
Robot string `help:"required if type if 'robot'"`
}
func (opts *SubscriptionSetReceiverOptions) Params() (jsonutils.JSONObject, error) {
d := jsonutils.NewDict()
if len(opts.Receivers) > 0 {
d.Set("receivers", jsonutils.NewStringArray(opts.Receivers))
}
if len(opts.RoleAndScopes) > 0 {
ra := jsonutils.NewArray()
for _, rs := range opts.RoleAndScopes {
index := strings.Index(rs, ":")
if index <= 0 {
return nil, fmt.Errorf("invalid role_and_scope %q, example: %s", rs, "admin:system")
}
rd := jsonutils.NewDict()
rd.Set("role", jsonutils.NewString(rs[:index]))
rd.Set("scope", jsonutils.NewString(rs[index+1:]))
ra.Add(rd)
}
d.Set("roles", ra)
}
return d, nil
func (sc *SubscriberCreateOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(sc), nil
}
type SsubscriptionSetRobotOptions struct {
ROBOT string `choices:"feishu-robot|dingtalk-robot|workwx-robot"`
type SubscriberListOptions struct {
options.BaseListOptions
TopicId string
ResourceScope string `choices:"system|domain|project"`
Type string `choices:"receiver|robot|role"`
}
type SubscriptionSetRobotOptions struct {
SubscriptionOptions
SsubscriptionSetRobotOptions
func (sl *SubscriberListOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(sl)
}
func (opts *SubscriptionSetRobotOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(opts.SsubscriptionSetRobotOptions), nil
type SubscriberOptions struct {
ID string
}
type SsubscriptionSetWebhookOptions struct {
WEBHOOK string `choices:"webhook"`
func (s *SubscriberOptions) GetId() string {
return s.ID
}
type SubscriptionSetWebhookOptions struct {
SubscriptionOptions
SsubscriptionSetWebhookOptions
func (s *SubscriberOptions) Params() (jsonutils.JSONObject, error) {
return nil, nil
}
func (opts *SubscriptionSetWebhookOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(opts.SsubscriptionSetWebhookOptions), nil
type SubscriberSetReceiverOptions struct {
SubscriberOptions
Receivers []string
}
func (ssr *SubscriberSetReceiverOptions) Params() (jsonutils.JSONObject, error) {
params := jsonutils.NewDict()
params.Set("receivers", jsonutils.NewStringArray(ssr.Receivers))
return params, nil
}
+21 -21
View File
@@ -182,12 +182,12 @@ func (lt *SLocalTemplateManager) fillWithTemplate(ctx context.Context, titleOrCo
}
var specFields = map[string][]string{
notify.SUBSCRIPTION_RESOURCE_SCALINGPOLICY: {
notify.TOPIC_RESOURCE_SCALINGPOLICY: {
"trigger_type",
"action",
"unit",
},
notify.SUBSCRIPTION_RESOURCE_SCHEDULEDTASK: {
notify.TOPIC_RESOURCE_SCHEDULEDTASK: {
"resource_type",
"operation",
},
@@ -212,8 +212,8 @@ func init() {
stI18nTable.Set(comapi.ST_RESOURCE_OPERATION_STOP, i18n.NewTableEntry().EN("stop").CN("关机"))
stI18nTable.Set(comapi.ST_RESOURCE_OPERATION_START, i18n.NewTableEntry().EN("start").CN("开机"))
specFieldTrans[notify.SUBSCRIPTION_RESOURCE_SCALINGPOLICY] = spI18nTable
specFieldTrans[notify.SUBSCRIPTION_RESOURCE_SCHEDULEDTASK] = stI18nTable
specFieldTrans[notify.TOPIC_RESOURCE_SCALINGPOLICY] = spI18nTable
specFieldTrans[notify.TOPIC_RESOURCE_SCHEDULEDTASK] = stI18nTable
}
func (lt *SLocalTemplateManager) getTemplate(ctx context.Context, titleOrContent string, contactType string, topic string, lang string) (*template.Template, error) {
@@ -307,87 +307,87 @@ func init() {
api.TEMPLATE_LANG_CN,
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_SERVER,
api.TOPIC_RESOURCE_SERVER,
"virtual machine",
"虚拟机",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_SCALINGGROUP,
api.TOPIC_RESOURCE_SCALINGGROUP,
"scaling group",
"弹性伸缩组",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_SCALINGPOLICY,
api.TOPIC_RESOURCE_SCALINGPOLICY,
"scaling policy",
"弹性伸缩策略",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_IMAGE,
api.TOPIC_RESOURCE_IMAGE,
"image",
"系统镜像",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_DISK,
api.TOPIC_RESOURCE_DISK,
"disk",
"硬盘",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_SNAPSHOT,
api.TOPIC_RESOURCE_SNAPSHOT,
"snapshot",
"硬盘快照",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_INSTANCESNAPSHOT,
api.TOPIC_RESOURCE_INSTANCESNAPSHOT,
"instance snapshot",
"主机快照",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_NETWORK,
api.TOPIC_RESOURCE_NETWORK,
"network",
"IP子网",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_EIP,
api.TOPIC_RESOURCE_EIP,
"EIP",
"弹性公网IP",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_SECGROUP,
api.TOPIC_RESOURCE_SECGROUP,
"security group",
"安全组",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_LOADBALANCER,
api.TOPIC_RESOURCE_LOADBALANCER,
"loadbalancer instance",
"负载均衡实例",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_LOADBALANCERACL,
api.TOPIC_RESOURCE_LOADBALANCERACL,
"loadbalancer ACL",
"负载均衡访问控制",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_LOADBALANCERCERTIFICATE,
api.TOPIC_RESOURCE_LOADBALANCERCERTIFICATE,
"loadbalancer certificate",
"负载均衡证书",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_BUCKET,
api.TOPIC_RESOURCE_BUCKET,
"object storage bucket",
"对象存储桶",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_DBINSTANCE,
api.TOPIC_RESOURCE_DBINSTANCE,
"RDS instance",
"RDS实例",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_ELASTICCACHE,
api.TOPIC_RESOURCE_ELASTICCACHE,
"Redis instance",
"Redis实例",
},
sI18nElme{
api.SUBSCRIPTION_RESOURCE_SCHEDULEDTASK,
api.TOPIC_RESOURCE_SCHEDULEDTASK,
"scheduled task",
"定时任务",
},
+2 -2
View File
@@ -32,8 +32,8 @@ func InitDB() error {
ConfigManager,
TemplateManager,
ReceiverNotificationManager,
SubscriptionManager,
RobotManager,
TopicManager,
RobotManager,
} {
err := manager.InitializeData()
if err != nil {
+48 -47
View File
@@ -232,16 +232,16 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred
}
// receiver
subscriptions, err := SubscriptionManager.SubsciptionByEvent(input.Event, input.AdvanceDays)
topics, err := TopicManager.TopicsByEvent(input.Event, input.AdvanceDays)
if err != nil {
return output, errors.Wrapf(err, "unable fetch subscriptions by event %q", input.Event)
}
if len(subscriptions) == 0 {
if len(topics) == 0 {
return output, nil
}
var receiverIds []string
for i := range subscriptions {
receiverIds1, err := SubscriptionReceiverManager.getReceivers(ctx, subscriptions[i].Id, input.ProjectDomainId, input.ProjectId)
for i := range topics {
receiverIds1, err := SubscriberManager.getReceiversSent(ctx, topics[i].Id, input.ProjectDomainId, input.ProjectId)
if err != nil {
return output, errors.Wrap(err, "unable to get receive")
}
@@ -252,14 +252,14 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred
// robot
var robots []string
for i := range subscriptions {
robot, err := SubscriptionReceiverManager.robot(subscriptions[i].Id)
for i := range topics {
_robots, err := SubscriberManager.robot(topics[i].Id, input.ProjectDomainId, input.ProjectId)
if err != nil {
if errors.Cause(err) != errors.ErrNotFound {
return output, errors.Wrapf(err, "unable fetch robot of subscription %q", subscriptions[i].Id)
return output, errors.Wrapf(err, "unable fetch robot of subscription %q", topics[i].Id)
}
} else {
robots = append(robots, robot)
robots = append(robots, _robots...)
}
}
robots = intersection(robots, intersection(cts, RobotContactTypes))
@@ -267,22 +267,6 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred
robots = sets.NewString(robots...).UnsortedList()
}
// webhook
var webhooks []string
for i := range subscriptions {
webhook, err := SubscriptionReceiverManager.webhook(subscriptions[i].Id)
if err != nil {
if errors.Cause(err) != errors.ErrNotFound {
return output, errors.Wrapf(err, "unable to fetch webhook of subscription %q", subscriptions[i].Id)
}
} else {
webhooks = append(webhooks, webhook)
}
}
if len(webhooks) > 0 {
webhooks = sets.NewString(webhooks...).UnsortedList()
}
message := jsonutils.Marshal(input.ResourceDetails).String()
// fillter non-existed receiver
@@ -313,39 +297,56 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred
}
// normal contact type
for _, ct := range contactTypes {
err := nm.create(ctx, userCred, ct, receiverIds, []string{}, input.Priority, "", message, input.Event, input.AdvanceDays)
err := nm.create(ctx, userCred, ct, receiverIds, nil, input.Priority, "", message, input.Event, input.AdvanceDays)
if err != nil {
output.FailedList = append(output.FailedList, api.FailedElem{
ContactType: ct,
Reason: err.Error(),
})
}
}
// robot
for _, robot := range robots {
err := nm.create(ctx, userCred, robot, []string{}, []string{}, input.Priority, "", message, input.Event, input.AdvanceDays)
if err != nil {
output.FailedList = append(output.FailedList, api.FailedElem{
ContactType: robot,
Reason: err.Error(),
})
}
}
// webhook
for _, webhook := range webhooks {
err := nm.create(ctx, userCred, webhook, []string{}, []string{}, input.Priority, "", message, input.Event, input.AdvanceDays)
if err != nil {
output.FailedList = append(output.FailedList, api.FailedElem{
ContactType: webhook,
Reason: err.Error(),
})
}
}
err = nm.createWithRobots(ctx, userCred, robots, input.Priority, "", message, input.Event, input.AdvanceDays)
output.FailedList = append(output.FailedList, api.FailedElem{
ContactType: api.ROBOT,
Reason: err.Error(),
})
return output, nil
}
func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred mcclient.TokenCredential, robotIds []string, priority, topic, message, event string, advanceDays int) error {
if len(robotIds) == 0 {
return nil
}
n := &SNotification{
ContactType: api.ROBOT,
Message: message,
Priority: priority,
ReceivedAt: time.Now(),
Event: event,
AdvanceDays: advanceDays,
}
n.Id = db.DefaultUUIDGenerator()
for i := range robotIds {
_, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, robotIds[i], n.Id)
if err != nil {
return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot")
}
}
err := nm.TableSpec().Insert(ctx, n)
if err != nil {
return errors.Wrap(err, "unable to insert Notification")
}
n.SetModelManager(nm, n)
task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
if err != nil {
log.Errorf("NotificationSendTask newTask error %v", err)
} else {
task.ScheduleRun(nil)
}
return nil
}
func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiverIds, contacts []string, priority, topic, message, event string, advanceDays int) error {
if len(receiverIds)+len(contacts) == 0 {
log.Infof("%s: no send", contactType)
@@ -374,7 +375,7 @@ func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.To
for i := range contacts {
_, err := ReceiverNotificationManager.CreateContact(ctx, userCred, contacts[i], n.Id)
if err != nil {
return errors.Wrap(err, "ReceiverNotificationManager.Create")
return errors.Wrap(err, "ReceiverNotificationManager.CreateContact")
}
}
log.Infof("start NotificationSendTask for %s", contactType)
+443
View File
@@ -0,0 +1,443 @@
// 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"
"sort"
"strings"
"golang.org/x/sync/errgroup"
"k8s.io/apimachinery/pkg/util/sets"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"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"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
var SubscriberManager *SSubscriberManager
func init() {
SubscriberManager = &SSubscriberManager{
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
SSubscriber{},
"subscriber_tbl",
"subscriber",
"subscribers",
),
}
SubscriberManager.SetVirtualObject(ReceiverNotificationManager)
}
type SSubscriberManager struct {
db.SVirtualResourceBaseManager
db.SEnabledResourceBaseManager
}
type SSubscriber struct {
db.SVirtualResourceBase
db.SEnabledResourceBase
TopicID string `width:"128" charset:"ascii" nullable:"false" index:"true" get:"user" list:"user"`
Type string `width:"16" charset:"ascii" nullable:"false" index:"true" get:"user" list:"user"`
Identification string `width:"128" charset:"ascii" nullable:"false"`
RoleScope string `width:"8" charset:"ascii" nullable:"false" get:"user" list:"user"`
ResourceScope string `width:"8" charset:"ascii" nullable:"false" get:"user" list:"user"`
}
func (sm *SSubscriberManager) validateReceivers(ctx context.Context, receivers []string) ([]string, error) {
rs, err := ReceiverManager.FetchByIdOrNames(ctx, receivers...)
if err != nil {
return nil, errors.Wrap(err, "unable to fetch Receivers")
}
reSet := sets.NewString(receivers...)
reIds := make([]string, len(rs))
for i := range rs {
reSet.Delete(rs[i].GetId())
reSet.Delete(rs[i].GetName())
reIds[i] = rs[i].GetId()
}
if reSet.Len() > 0 {
return nil, httperrors.NewInputParameterError("receivers %q not found", strings.Join(reSet.UnsortedList(), ", "))
}
return reIds, nil
}
func (sm *SSubscriberManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.SubscriberCreateInput) (api.SubscriberCreateInput, error) {
var err error
input.VirtualResourceCreateInput, err = sm.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.VirtualResourceCreateInput)
if err != nil {
return input, errors.Wrap(err, "SVirtualResourceBaseManager.ValidateCreateData")
}
// check topic
t, err := TopicManager.FetchById(input.TopicID)
if err != nil {
return input, errors.Wrapf(err, "unable to fetch topic %s", input.TopicID)
}
// check resource scope
if !utils.IsInStringArray(input.ResourceScope, []string{api.SUBSCRIBER_SCOPE_SYSTEM, api.SUBSCRIBER_SCOPE_DOMAIN, api.SUBSCRIBER_SCOPE_PROJECT}) {
return input, httperrors.NewInputParameterError("unknown resource_scope %q", input.ResourceScope)
}
input.TopicID = t.GetId()
switch input.Type {
case api.SUBSCRIBER_TYPE_RECEIVER:
reIds, err := sm.validateReceivers(ctx, input.Receivers)
if err != nil {
return input, err
}
input.Receivers = reIds
case api.SUBSCRIBER_TYPE_ROLE:
if input.RoleScope == "" {
input.RoleScope = input.ResourceScope
}
roleCache, err := db.RoleCacheManager.FetchRoleByIdOrName(ctx, input.Role)
if err != nil {
return input, errors.Wrapf(err, "unable find role %q", input.Role)
}
input.Role = roleCache.GetId()
case api.SUBSCRIBER_TYPE_ROBOT:
robot, err := RobotManager.FetchByIdOrName(userCred, input.Robot)
if errors.Cause(err) == sql.ErrNoRows {
return input, httperrors.NewInputParameterError("robot %q not found", input.Robot)
}
if err != nil {
return input, errors.Wrapf(err, "unable to fetch robot %q", input.Robot)
}
input.Robot = robot.GetId()
default:
return input, httperrors.NewInputParameterError("unkown type %q", input.Type)
}
return input, nil
}
func (s *SSubscriber) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
err := s.SVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data)
if err != nil {
return errors.Wrap(err, "SVirtualResourceBase.CustomizeCreate")
}
var input api.SubscriberCreateInput
_ = data.Unmarshal(&input)
switch input.Type {
case api.SUBSCRIBER_TYPE_RECEIVER:
err := s.SetReceivers(ctx, input.Receivers)
if err != nil {
return errors.Wrapf(err, "unable to set connect receivers with subscriber %s", s.Id)
}
case api.SUBSCRIBER_TYPE_ROBOT:
s.Identification = input.Robot
case api.SUBSCRIBER_TYPE_ROLE:
s.Identification = input.Role
}
return nil
}
func (sm *SSubscriberManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.SubscriberListInput) (*sqlchemy.SQuery, error) {
var err error
q, err = sm.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.VirtualResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemFilter")
}
q, err = sm.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledResourceBaseListInput)
if err != nil {
return nil, errors.Wrap(err, "SEnabledResourceBaseManager.ListItemFilter")
}
if input.TopicID != "" {
q = q.Equals("topic_id", input.TopicID)
}
if input.Type != "" {
q = q.Equals("type", input.Type)
}
if input.ResourceScope != "" {
q = q.Equals("resource_scope", input.ResourceScope)
}
return q, nil
}
func (sm *SSubscriberManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.SubscriberDetails {
var err error
vRows := sm.SVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
rows := make([]api.SubscriberDetails, len(objs))
for i := range rows {
rows[i].VirtualResourceDetails = vRows[i]
s := objs[i].(*SSubscriber)
switch s.Type {
case api.SUBSCRIBER_TYPE_RECEIVER:
rows[i].Receivers, err = s.receiverIdentifications()
if err != nil {
log.Errorf("unable to get receiverIdentifications for subscriber %q: %v", s.Id, err)
}
case api.SUBSCRIBER_TYPE_ROBOT:
rows[i].Robot, err = s.robotIdentification()
if err != nil {
log.Errorf("unable get robotIdentification for subscriber %q: %v", s.Id, err)
}
case api.SUBSCRIBER_TYPE_ROLE:
rows[i].Role, err = s.roleIdentification()
if err != nil {
log.Errorf("unable to get roleIdentification for subscriber %q: %v", s.Id, err)
}
}
}
return rows
}
func (s *SSubscriber) receiverIdentifications() ([]api.Identification, error) {
srSubq := SubscriberReceiverManager.Query().Equals("subscription_id", s.Id).SubQuery()
rq := ReceiverManager.Query("id", "name")
rq = rq.Join(srSubq, sqlchemy.Equals(srSubq.Field("receiver_id"), rq.Field("id")))
var ret []api.Identification
err := rq.All(&ret)
if err != nil {
return nil, err
}
return ret, nil
}
func (s *SSubscriber) robotIdentification() (api.Identification, error) {
var ret api.Identification
q := RobotManager.Query("id", "name").Equals("id", s.Identification)
err := q.First(&ret)
if err != nil {
return ret, err
}
return ret, nil
}
func (s *SSubscriber) roleIdentification() (api.Identification, error) {
var ret api.Identification
q := db.RoleCacheManager.Query("id", "name").Equals("id", s.Identification)
err := q.First(&ret)
if err != nil {
return ret, err
}
return ret, nil
}
func (srm *SSubscriberManager) robot(tid, projectDomainId, projectId string) ([]string, error) {
srs, err := srm.findSuitableOnes(tid, projectDomainId, projectId, api.SUBSCRIBER_TYPE_ROBOT)
if err != nil {
return nil, err
}
robotIds := make([]string, len(srs))
for i := range srs {
robotIds[i] = srs[i].Identification
}
return robotIds, nil
}
func (srm *SSubscriberManager) findSuitableOnes(tid, projectDomainId, projectId string, types ...string) ([]SSubscriber, error) {
q := srm.Query().Equals("subscription_id", tid)
q = q.Filter(sqlchemy.OR(
sqlchemy.AND(
sqlchemy.Equals(q.Field("resource_type"), api.SUBSCRIBER_SCOPE_PROJECT),
sqlchemy.Equals(q.Field("project_id"), projectId),
),
sqlchemy.AND(
sqlchemy.Equals(q.Field("resource_type"), api.SUBSCRIBER_SCOPE_DOMAIN),
sqlchemy.Equals(q.Field("domain_id"), projectDomainId),
),
sqlchemy.Equals(q.Field("resource_type"), api.SUBSCRIBER_SCOPE_SYSTEM),
))
switch len(types) {
case 0:
case 1:
q = q.Equals("type", types[0])
default:
q = q.In("type", types)
}
srs := make([]SSubscriber, 0, 1)
err := db.FetchModelObjects(srm, q, &srs)
if err != nil {
return nil, err
}
return srs, nil
}
// TODO: Use cache to increase speed
func (srm *SSubscriberManager) getReceiversSent(ctx context.Context, tid string, projectDomainId string, projectId string) ([]string, error) {
srs, err := srm.findSuitableOnes(tid, projectDomainId, projectId, api.SUBSCRIBER_TYPE_RECEIVER, api.SUBSCRIBER_TYPE_ROLE)
if err != nil {
return nil, err
}
receivers := make([]string, 0, len(srs))
roleMap := make(map[string][]string, 3)
receivermap := make(map[string]*[]string, 3)
for _, sr := range srs {
if sr.Type == api.SUBSCRIBER_TYPE_RECEIVER {
rIds, err := sr.getReceivers()
if err != nil {
return nil, errors.Wrap(err, "unable to get receivers")
}
receivers = append(receivers, rIds...)
} else if sr.Type == api.SUBSCRIBER_TYPE_ROBOT {
roleMap[sr.RoleScope] = append(roleMap[sr.RoleScope], sr.Identification)
receivermap[sr.RoleScope] = &[]string{}
}
}
errgo, _ := errgroup.WithContext(ctx)
for _scope, _roles := range roleMap {
scope, roles := _scope, _roles
receivers := receivermap[scope]
errgo.Go(func() error {
query := jsonutils.NewDict()
query.Set("roles", jsonutils.NewStringArray(roles))
query.Set("effective", jsonutils.JSONTrue)
switch scope {
case api.SUBSCRIBER_SCOPE_SYSTEM:
case api.SUBSCRIBER_SCOPE_DOMAIN:
if len(projectDomainId) == 0 {
return fmt.Errorf("need projectDomainId")
}
query.Set("project_domain_id", jsonutils.NewString(projectDomainId))
case api.SUBSCRIBER_SCOPE_PROJECT:
if len(projectId) == 0 {
return fmt.Errorf("need projectId")
}
query.Add(jsonutils.NewString(projectId), "scope", "project", "id")
}
s := auth.GetAdminSession(ctx, "", "")
log.Debugf("query for role-assignments: %s", query.String())
listRet, err := modules.RoleAssignments.List(s, query)
if err != nil {
return errors.Wrap(err, "unable to list RoleAssignments")
}
log.Debugf("return value for role-assignments: %s", jsonutils.Marshal(listRet))
for i := range listRet.Data {
ras := listRet.Data[i]
user, err := ras.Get("user")
if err == nil {
id, err := user.GetString("id")
if err != nil {
return errors.Wrap(err, "unable to get user.id from result of RoleAssignments.List")
}
*receivers = append(*receivers, id)
}
}
return nil
})
}
err = errgo.Wait()
if err != nil {
return nil, err
}
for _, res := range receivermap {
receivers = append(receivers, *res...)
}
// de-duplication
return sets.NewString(receivers...).UnsortedList(), nil
}
func (sr *SSubscriber) getReceivers() ([]string, error) {
srrs, err := SubscriberReceiverManager.getBySubscriberId(sr.Id)
if err != nil {
return nil, err
}
rIds := make([]string, len(srrs))
for i := range srrs {
rIds[i] = srrs[i].ReceiverId
}
return rIds, nil
}
func (sr *SSubscriber) SetReceivers(ctx context.Context, receiverIds []string) error {
srrs, err := SubscriberReceiverManager.getBySubscriberId(sr.Id)
if err != nil {
return errors.Wrapf(err, "unable to get SRReceiver by Subscriber %s", sr.Id)
}
dbReceivers := make([]string, len(srrs))
for i := range srrs {
dbReceivers[i] = srrs[i].ReceiverId
}
var addReceivers, rmReceivers []string
sort.Strings(dbReceivers)
sort.Strings(receiverIds)
for i, j := 0, 0; i < len(dbReceivers) || j < len(receiverIds); {
switch {
case i == len(dbReceivers):
addReceivers = append(addReceivers, receiverIds[j])
j++
case j == len(receiverIds):
rmReceivers = append(rmReceivers, dbReceivers[i])
i++
case dbReceivers[i] > receiverIds[j]:
rmReceivers = append(rmReceivers, dbReceivers[i])
i++
case dbReceivers[i] < receiverIds[j]:
addReceivers = append(addReceivers, receiverIds[j])
j++
case dbReceivers[i] == receiverIds[j]:
}
}
// add
for _, rId := range addReceivers {
_, err := SubscriberReceiverManager.create(ctx, sr.Id, rId)
if err != nil {
return errors.Wrapf(err, "unable to connect subscription receiver %q with receiver %q", sr.Id, rId)
}
}
for _, rId := range rmReceivers {
err := SubscriberReceiverManager.delete(sr.Id, rId)
if err != nil {
return errors.Wrapf(err, "unable to disconnect subscription receiver %q with receiver %q", sr.Id, rId)
}
}
return nil
}
func (s *SSubscriber) AllowPerformSetReceiver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return s.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, s, "set-receiver")
}
func (s *SSubscriber) PerformSetReceiver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.SubscriberSetReceiverInput) (jsonutils.JSONObject, error) {
reIds, err := SubscriberManager.validateReceivers(ctx, input.Receivers)
if err != nil {
return nil, err
}
return nil, s.SetReceivers(ctx, reIds)
}
func (s *SSubscriber) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, s, "enable")
}
func (s *SSubscriber) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) {
err := db.EnabledPerformEnable(s, ctx, userCred, true)
if err != nil {
return nil, errors.Wrap(err, "EnabledPerformEnable")
}
return nil, nil
}
func (s *SSubscriber) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, s, "disable")
}
func (s *SSubscriber) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) {
err := db.EnabledPerformEnable(s, ctx, userCred, false)
if err != nil {
return nil, errors.Wrap(err, "EnabledPerformEnable")
}
return nil, nil
}
+80
View File
@@ -0,0 +1,80 @@
// 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/onecloud/pkg/cloudcommon/db"
)
var SubscriberReceiverManager *SSubscriberReceiverManager
func init() {
SubscriberReceiverManager = &SSubscriberReceiverManager{
SJointResourceBaseManager: db.NewJointResourceBaseManager(
SSubscriberReceiver{},
"subscriber_receiver_tbl",
"subscriber_receiver",
"subscriber_receivers",
SubscriberManager,
ReceiverManager,
),
}
SubscriberReceiverManager.SetVirtualObject(SubscriberReceiverManager)
}
type SSubscriberReceiverManager struct {
db.SJointResourceBaseManager
}
type SSubscriberReceiver struct {
db.SJointResourceBase
SubscriberId string `width:"36" charset:"ascii" nullable:"false" index:"true"`
ReceiverId string `width:"36" charset:"ascii" nullable:"false" index:"true"`
}
func (srm *SSubscriberReceiverManager) getBySubscriberId(sId string) ([]SSubscriberReceiver, error) {
q := srm.Query().Equals("subscriber_id", sId)
srs := make([]SSubscriberReceiver, 0, 2)
err := db.FetchModelObjects(srm, q, &srs)
if err != nil {
return nil, err
}
return srs, nil
}
func (srm *SSubscriberReceiverManager) create(ctx context.Context, sId, receiverId string) (*SSubscriberReceiver, error) {
sr := &SSubscriberReceiver{
SubscriberId: sId,
ReceiverId: receiverId,
}
return sr, srm.TableSpec().Insert(ctx, sr)
}
func (srm *SSubscriberReceiverManager) delete(sId, receiverId string) error {
q := srm.Query().Equals("receiver_id", receiverId)
var sr SSubscriberReceiver
err := q.First(&sr)
if err != nil {
return err
}
srp := &sr
_, err = db.Update(srp, func() error {
srp.MarkDelete()
return nil
})
return err
}
-577
View File
@@ -1,577 +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"
"golang.org/x/sync/errgroup"
"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/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/util/bitmap"
"yunion.io/x/onecloud/pkg/util/rbacutils"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
func parseEvent(es string) (notify.SEvent, error) {
ess := strings.Split(es, notify.DelimiterInEvent)
if len(ess) != 2 {
return notify.SEvent{}, fmt.Errorf("invalid event string %q", es)
}
return notify.Event.WithResourceType(ess[0]).WithAction(notify.SAction(ess[1])), nil
}
type SSubscriptionManager struct {
db.SStandaloneResourceBaseManager
db.SEnabledResourceBaseManager
}
var SubscriptionManager *SSubscriptionManager
func init() {
SubscriptionManager = &SSubscriptionManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SSubscription{},
"subscription_tbl",
"subscription",
"subscriptions",
),
}
SubscriptionManager.SetVirtualObject(SubscriptionManager)
}
type SSubscription struct {
db.SStandaloneResourceBase
db.SEnabledResourceBase
Type string `width:"20" nullable:"false" create:"required" update:"user" list:"user"`
Resources uint64 `nullable:"false"`
Actions uint32 `nullable:"false"`
AdvanceDays int `nullable:"false"`
}
const (
DefaultResourceCreateDelete = "resource create or delete"
DefaultResourceChangeConfig = "resource change config"
DefaultResourceUpdate = "resource update"
DefaultResourceReleaseDue1Day = "resource release due 1 day"
DefaultResourceReleaseDue3Day = "resource release due 3 day"
DefaultScheduledTaskExecute = "scheduled task execute"
DefaultScalingPolicyExecute = "scaling policy execute"
DefaultSnapshotPolicyExecute = "snapshot policy execute"
)
func (sm *SSubscriptionManager) InitializeData() error {
initSNames := sets.NewString(
DefaultResourceCreateDelete,
DefaultResourceChangeConfig,
DefaultResourceUpdate,
DefaultResourceReleaseDue1Day,
DefaultResourceReleaseDue3Day,
DefaultScheduledTaskExecute,
DefaultScalingPolicyExecute,
DefaultSnapshotPolicyExecute,
)
q := sm.Query()
subscriptions := make([]SSubscription, 0, initSNames.Len())
err := db.FetchModelObjects(sm, q, &subscriptions)
if err != nil {
return errors.Wrap(err, "unable to FetchModelObjects")
}
for i := range subscriptions {
ss := &subscriptions[i]
initSNames.Delete(ss.Name)
}
ctx := context.Background()
for _, name := range initSNames.UnsortedList() {
ss := new(SSubscription)
ss.Name = name
switch name {
case DefaultResourceCreateDelete:
ss.addResources(
notify.SUBSCRIPTION_RESOURCE_SERVER,
notify.SUBSCRIPTION_RESOURCE_SCALINGGROUP,
notify.SUBSCRIPTION_RESOURCE_IMAGE,
notify.SUBSCRIPTION_RESOURCE_DISK,
notify.SUBSCRIPTION_RESOURCE_SNAPSHOT,
notify.SUBSCRIPTION_RESOURCE_INSTANCESNAPSHOT,
notify.SUBSCRIPTION_RESOURCE_SNAPSHOTPOLICY,
notify.SUBSCRIPTION_RESOURCE_NETWORK,
notify.SUBSCRIPTION_RESOURCE_EIP,
notify.SUBSCRIPTION_RESOURCE_LOADBALANCER,
notify.SUBSCRIPTION_RESOURCE_LOADBALANCERACL,
notify.SUBSCRIPTION_RESOURCE_LOADBALANCERCERTIFICATE,
notify.SUBSCRIPTION_RESOURCE_BUCKET,
notify.SUBSCRIPTION_RESOURCE_DBINSTANCE,
notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE,
)
ss.addAction(
notify.ActionCreate,
notify.ActionDelete,
notify.ActionPendingDelete,
)
ss.Type = notify.SUBSCRIPTION_TYPE_RESOURCE
case DefaultResourceChangeConfig:
ss.addResources(
notify.SUBSCRIPTION_RESOURCE_SERVER,
notify.SUBSCRIPTION_RESOURCE_DISK,
notify.SUBSCRIPTION_RESOURCE_DBINSTANCE,
notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE,
)
ss.addAction(notify.ActionChangeConfig)
ss.Type = notify.SUBSCRIPTION_TYPE_RESOURCE
case DefaultResourceUpdate:
ss.addResources(
notify.SUBSCRIPTION_RESOURCE_SERVER,
notify.SUBSCRIPTION_RESOURCE_DISK,
notify.SUBSCRIPTION_RESOURCE_DBINSTANCE,
notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE,
)
ss.addAction(notify.ActionUpdate)
ss.Type = notify.SUBSCRIPTION_TYPE_RESOURCE
case DefaultResourceReleaseDue1Day:
ss.addResources(
notify.SUBSCRIPTION_RESOURCE_SERVER,
notify.SUBSCRIPTION_RESOURCE_DISK,
notify.SUBSCRIPTION_RESOURCE_EIP,
notify.SUBSCRIPTION_RESOURCE_LOADBALANCER,
notify.SUBSCRIPTION_RESOURCE_DBINSTANCE,
notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE,
)
ss.addAction(notify.ActionExpiredRelease)
ss.Type = notify.SUBSCRIPTION_TYPE_RESOURCE
ss.AdvanceDays = 1
case DefaultResourceReleaseDue3Day:
ss.addResources(
notify.SUBSCRIPTION_RESOURCE_SERVER,
notify.SUBSCRIPTION_RESOURCE_DISK,
notify.SUBSCRIPTION_RESOURCE_EIP,
notify.SUBSCRIPTION_RESOURCE_LOADBALANCER,
notify.SUBSCRIPTION_RESOURCE_DBINSTANCE,
notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE,
)
ss.addAction(notify.ActionExpiredRelease)
ss.Type = notify.SUBSCRIPTION_TYPE_RESOURCE
ss.AdvanceDays = 3
case DefaultScheduledTaskExecute:
ss.addResources(notify.SUBSCRIPTION_RESOURCE_SCHEDULEDTASK)
ss.addAction(notify.ActionExecute)
ss.Type = notify.SUBSCRIPTION_TYPE_AUTOMATED_PROCESS
case DefaultScalingPolicyExecute:
ss.addResources(notify.SUBSCRIPTION_RESOURCE_SCALINGPOLICY)
ss.addAction(notify.ActionExecute)
ss.Type = notify.SUBSCRIPTION_TYPE_AUTOMATED_PROCESS
case DefaultSnapshotPolicyExecute:
ss.addResources(notify.SUBSCRIPTION_RESOURCE_SNAPSHOTPOLICY)
ss.addAction(notify.ActionExecute)
ss.Type = notify.SUBSCRIPTION_TYPE_AUTOMATED_PROCESS
}
err := sm.TableSpec().Insert(ctx, ss)
if err != nil {
return errors.Wrapf(err, "unable to insert %s", name)
}
}
return nil
}
func (sm *SSubscriptionManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input notify.SubscriptionListInput) (*sqlchemy.SQuery, error) {
return q, nil
}
func (sm *SSubscriptionManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []notify.SubscriptionDetails {
sRows := sm.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
rows := make([]notify.SubscriptionDetails, len(objs))
for i := range rows {
rows[i].StandaloneResourceDetails = sRows[i]
ss := objs[i].(*SSubscription)
rows[i].Resources = ss.getResources()
srs, err := ss.subscriptionReceiverDiss()
if err != nil {
log.Errorf("unable to get subscriptionReceivers: %v", err)
}
for j := range srs {
sr := &srs[j]
switch sr.ReceiverType {
case ReceiverNormal:
rows[i].Receivers.Receivers = append(rows[i].Receivers.Receivers, notify.IDAndName{
ID: sr.Receiver,
Name: sr.ReceiverName,
})
case ReceiverRole:
rows[i].Receivers.ReceivingRoles = append(rows[i].Receivers.ReceivingRoles, notify.ReceivingRoleIDAndName{
IDAndName: notify.IDAndName{
ID: sr.Receiver,
Name: sr.RoleName,
},
Scope: sr.RoleScope,
})
case ReceiverDingtalkRobot, ReceiverFeishuRobot, ReceiverWorkwxRobot:
rows[i].Robot = sr.ReceiverType
case ReceiverWeebhook:
rows[i].Webhook = sr.ReceiverType
}
}
}
return rows
}
type SSubscriptionReceiverDis struct {
SSubscriptionReceiver
ReceiverName string `json:"receiver_name"`
RoleName string `json:"role_name"`
}
func (s *SSubscription) subscriptionReceiverDiss() ([]SSubscriptionReceiverDis, error) {
q := SubscriptionReceiverManager.Query().Equals("subscription_id", s.Id)
rq := ReceiverManager.Query("id", "name").SubQuery()
roq := db.RoleCacheManager.Query("id", "name").SubQuery()
q = q.LeftJoin(rq, sqlchemy.Equals(q.Field("receiver"), rq.Field("id")))
q = q.LeftJoin(roq, sqlchemy.Equals(q.Field("receiver"), roq.Field("id")))
// It looks strange, but the order of append cannot be changed
q.AppendField(q.QueryFields()...)
q.AppendField(rq.Field("name", "receiver_name"))
q.AppendField(roq.Field("name", "role_name"))
srs := make([]SSubscriptionReceiverDis, 0)
err := q.All(&srs)
if err != nil {
return nil, errors.Wrap(err, "unable to fetch All")
}
return srs, nil
}
func (sm *SSubscriptionManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return nil, httperrors.NewForbiddenError("prohibit creation")
}
func (ss *SSubscription) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return input, httperrors.NewForbiddenError("update prohibited")
}
func (ss *SSubscription) ValidateDeleteCondition(ctx context.Context) error {
return httperrors.NewForbiddenError("prohibit deletion")
}
func (ss *SSubscription) AllowPerformSetReceiver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, ss, "set-receiver")
}
func (ss *SSubscription) PerformSetReceiver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input notify.SubscriptionSetReceiverInput) (jsonutils.JSONObject, error) {
// check input
errgo, _ := errgroup.WithContext(ctx)
// check receiving roles
validScopes := []string{string(rbacutils.ScopeSystem), string(rbacutils.ScopeDomain), string(rbacutils.ScopeProject)}
for i := range input.ReceivingRoles {
role := input.ReceivingRoles[i]
index := i
errgo.Go(func() error {
if len(role.Scope) == 0 {
return httperrors.NewInputParameterError("empty scope for role %q", role.Role)
}
if !utils.IsInStringArray(role.Scope, validScopes) {
return httperrors.NewInputParameterError("invalid scope %q for role %q, need %s, %s or %s", role.Scope, role.Role, rbacutils.ScopeSystem, rbacutils.ScopeDomain, rbacutils.ScopeProject)
}
roleCache, err := db.RoleCacheManager.FetchRoleByIdOrName(ctx, role.Role)
if err != nil {
return errors.Wrapf(err, "unable find role %q", role)
}
input.ReceivingRoles[index].Role = roleCache.GetId()
return nil
})
}
err := errgo.Wait()
if err != nil {
return nil, err
}
receivers, err := ReceiverManager.FetchByIdOrNames(ctx, input.Receivers...)
if err != nil {
return nil, errors.Wrap(err, "unable to fetch Receivers")
}
reSet := sets.NewString(input.Receivers...)
reIds := make([]string, len(receivers))
for i := range receivers {
reSet.Delete(receivers[i].GetId())
reSet.Delete(receivers[i].GetName())
reIds[i] = receivers[i].GetId()
}
if reSet.Len() > 0 {
return nil, httperrors.NewInputParameterError("receivers %q not found", strings.Join(reSet.UnsortedList(), ", "))
}
input.Receivers = reIds
// deal with subscriptionReceivers
srs, err := SubscriptionReceiverManager.findReceivers(ss.Id, ReceiverNormal, ReceiverRole)
if err != nil {
return nil, errors.Wrap(err, "unable to findReceivers")
}
reSet = sets.NewString(input.Receivers...)
reRoleSet := make(map[notify.ReceivingRole]struct{})
for _, role := range input.ReceivingRoles {
reRoleSet[role] = struct{}{}
}
for i := range srs {
rs := &srs[i]
switch rs.ReceiverType {
case ReceiverNormal:
if !reSet.Has(rs.Receiver) {
err := rs.Delete(ctx, userCred)
if err != nil {
return nil, errors.Wrapf(err, "unable to delete receiver %s", rs.Receiver)
}
}
reSet.Delete(rs.Receiver)
case ReceiverRole:
role := rs.receivingRole()
if _, ok := reRoleSet[role]; !ok {
err := rs.Delete(ctx, userCred)
if err != nil {
return nil, errors.Wrapf(err, "unable to delete receiver %s", rs.Receiver)
}
}
delete(reRoleSet, role)
}
}
for _, re := range reSet.UnsortedList() {
_, err := SubscriptionReceiverManager.create(ctx, ss.Id, re, ReceiverNormal, "")
if err != nil {
return nil, errors.Wrapf(err, "unable to create receiver %s", re)
}
}
for role := range reRoleSet {
_, err := SubscriptionReceiverManager.create(ctx, ss.Id, role.Role, ReceiverRole, role.Scope)
if err != nil {
return nil, errors.Wrapf(err, "unable to create role %s", role)
}
}
return nil, nil
}
func (ss *SSubscription) AllowPerformSetRobot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, ss, "set-robot")
}
func (ss *SSubscription) PerformSetRobot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input notify.SubscriptionSetRobotInput) (jsonutils.JSONObject, error) {
err := ss.setSingleReceiver(ctx, input.Robot, ReceiverDingtalkRobot, ReceiverFeishuRobot, ReceiverWorkwxRobot)
if errors.Cause(err) == errors.ErrNotFound {
return nil, httperrors.NewInputParameterError("unkown robot %q", input.Robot)
}
return nil, err
}
func (ss *SSubscription) AllowPerformSetWebhook(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, ss, "set-webhook")
}
func (ss *SSubscription) PerformSetWebhook(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input notify.SubscriptionSetWebhookInput) (jsonutils.JSONObject, error) {
err := ss.setSingleReceiver(ctx, input.Webhook, ReceiverWeebhook)
if errors.Cause(err) == errors.ErrNotFound {
return nil, httperrors.NewInputParameterError("unkown webhook %q", input.Webhook)
}
return nil, err
}
func (ss *SSubscription) setSingleReceiver(ctx context.Context, re string, reTypes ...string) error {
receiverRobot := reTypes
if !utils.IsInStringArray(re, receiverRobot) {
return errors.ErrNotFound
}
srs, err := SubscriptionReceiverManager.findReceivers(ss.Id, receiverRobot...)
if err != nil {
return errors.Wrap(err, "unable to findReceivers")
}
if len(srs) == 0 {
// create one
_, err := SubscriptionReceiverManager.create(ctx, ss.Id, "", re, "")
return err
}
if len(srs) > 1 {
return fmt.Errorf("multi robot receiver")
}
// update
sr := &srs[0]
_, err = db.Update(sr, func() error {
sr.ReceiverType = re
return nil
})
return err
}
func (s *SSubscription) addResources(resources ...string) {
for _, resource := range resources {
v := converter.resourceValue(resource)
if v < 0 {
continue
}
s.Resources += 1 << v
}
}
func (s *SSubscription) addAction(actions ...notify.SAction) {
for _, action := range actions {
v := converter.actionValue(action)
if v < 0 {
continue
}
s.Actions += 1 << v
}
}
func (s *SSubscription) getResources() []string {
vs := bitmap.Uint64ToIntArray(s.Resources)
resources := make([]string, 0, len(vs))
for _, v := range vs {
resources = append(resources, converter.resource(v))
}
return resources
}
func (s *SSubscription) getActions() []notify.SAction {
vs := bitmap.Uint2IntArray(s.Actions)
actions := make([]notify.SAction, 0, len(vs))
for _, v := range vs {
actions = append(actions, converter.action(v))
}
return actions
}
func (sm *SSubscriptionManager) SubsciptionByEvent(eventStr string, advanceDays int) ([]SSubscription, error) {
event, err := parseEvent(eventStr)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse event %q", event)
}
resourceV := converter.resourceValue(event.ResourceType())
actionV := converter.actionValue(event.Action())
q := sm.Query().Equals("advance_days", advanceDays)
q = q.Filter(sqlchemy.GT(sqlchemy.AND_Val("", q.Field("resources"), 1<<resourceV), 0))
q = q.Filter(sqlchemy.GT(sqlchemy.AND_Val("", q.Field("actions"), 1<<actionV), 0))
var subscriptions []SSubscription
err = db.FetchModelObjects(sm, q, &subscriptions)
if err != nil {
q.DebugQuery()
return nil, errors.Wrap(err, "unable to FetchModelObjects")
}
return subscriptions, nil
}
func init() {
converter = &sConverter{
resourceValueMap: make(map[string]int, 5),
resourceList: make([]string, 0, 5),
actionList: make([]notify.SAction, 0, 5),
actionValueMap: make(map[notify.SAction]int, 5),
}
converter.registerResource(
notify.SUBSCRIPTION_RESOURCE_SERVER,
notify.SUBSCRIPTION_RESOURCE_SCALINGGROUP,
notify.SUBSCRIPTION_RESOURCE_SCALINGPOLICY,
notify.SUBSCRIPTION_RESOURCE_IMAGE,
notify.SUBSCRIPTION_RESOURCE_DISK,
notify.SUBSCRIPTION_RESOURCE_SNAPSHOT,
notify.SUBSCRIPTION_RESOURCE_INSTANCESNAPSHOT,
notify.SUBSCRIPTION_RESOURCE_SNAPSHOTPOLICY,
notify.SUBSCRIPTION_RESOURCE_NETWORK,
notify.SUBSCRIPTION_RESOURCE_EIP,
notify.SUBSCRIPTION_RESOURCE_SECGROUP,
notify.SUBSCRIPTION_RESOURCE_LOADBALANCER,
notify.SUBSCRIPTION_RESOURCE_LOADBALANCERACL,
notify.SUBSCRIPTION_RESOURCE_LOADBALANCERCERTIFICATE,
notify.SUBSCRIPTION_RESOURCE_BUCKET,
notify.SUBSCRIPTION_RESOURCE_DBINSTANCE,
notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE,
notify.SUBSCRIPTION_RESOURCE_SCHEDULEDTASK,
)
converter.registerAction(
notify.ActionCreate,
notify.ActionDelete,
notify.ActionPendingDelete,
notify.ActionUpdate,
notify.ActionRebuildRoot,
notify.ActionResetPassword,
notify.ActionChangeConfig,
notify.ActionExpiredRelease,
notify.ActionExecute,
)
}
var converter *sConverter
type sConverter struct {
resourceValueMap map[string]int
resourceList []string
actionValueMap map[notify.SAction]int
actionList []notify.SAction
}
func (rc *sConverter) registerResource(resources ...string) {
for _, resource := range resources {
if _, ok := rc.resourceValueMap[resource]; ok {
return
}
rc.resourceList = append(rc.resourceList, resource)
rc.resourceValueMap[resource] = len(rc.resourceList) - 1
}
}
func (rc *sConverter) registerAction(actions ...notify.SAction) {
for _, action := range actions {
if _, ok := rc.actionValueMap[action]; ok {
return
}
rc.actionList = append(rc.actionList, action)
rc.actionValueMap[action] = len(rc.actionList) - 1
}
}
func (rc *sConverter) resourceValue(resource string) int {
v, ok := rc.resourceValueMap[resource]
if !ok {
return -1
}
return v
}
func (rc *sConverter) resource(resourceValue int) string {
if resourceValue < 0 || resourceValue >= len(rc.resourceList) {
return ""
}
return rc.resourceList[resourceValue]
}
func (rc *sConverter) actionValue(action notify.SAction) int {
v, ok := rc.actionValueMap[action]
if !ok {
return -1
}
return v
}
func (rc *sConverter) action(actionValue int) notify.SAction {
if actionValue < 0 || actionValue >= len(rc.actionList) {
return notify.SAction("")
}
return rc.actionList[actionValue]
}
-201
View File
@@ -1,201 +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"
"golang.org/x/sync/errgroup"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/apis/notify"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules"
)
var SubscriptionReceiverManager *SSubscriptionReceiverManager
func init() {
SubscriptionReceiverManager = &SSubscriptionReceiverManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SSubscriptionReceiver{},
"subscriptionreceiver_tbl",
"subscriptionreceiver",
"subscriptionreceivers",
),
}
SubscriptionReceiverManager.SetVirtualObject(ReceiverNotificationManager)
}
type SSubscriptionReceiverManager struct {
db.SStandaloneResourceBaseManager
}
type SSubscriptionReceiver struct {
db.SStandaloneResourceBase
SubscriptionID string `width:"128" charset:"ascii" nullable:"fase" index:"true"`
// role id or receiver id or other and the value type is determined by the ReceiverType
Receiver string `width:"128" charset:"ascii" nullable:"false"`
ReceiverType string `width:"16" charset:"ascii" nullable:"false" index:"true"`
RoleScope string `width:"8" charset:"ascii" nullable:"false"`
}
const (
ReceiverRole = "role"
ReceiverNormal = "normal"
ReceiverFeishuRobot = notify.FEISHU_ROBOT
ReceiverDingtalkRobot = notify.DINGTALK_ROBOT
ReceiverWorkwxRobot = notify.WORKWX_ROBOT
ReceiverWeebhook = "webhook"
ScopeSystem = "system"
ScopeDomain = "domain"
ScopeProject = "project"
)
func (srm *SSubscriptionReceiverManager) robot(ssid string) (string, error) {
return srm.findSingleReceiver(ReceiverFeishuRobot, ReceiverDingtalkRobot, ReceiverWorkwxRobot)
}
func (srm *SSubscriptionReceiverManager) webhook(ssid string) (string, error) {
return srm.findSingleReceiver(ssid, ReceiverWeebhook)
}
func (srm *SSubscriptionReceiverManager) findSingleReceiver(ssid string, receiverTypes ...string) (string, error) {
srs, err := srm.findReceivers(ssid, receiverTypes...)
if err != nil {
return "", err
}
if len(srs) > 1 {
return "", errors.Error("multi receiver")
}
if len(srs) == 0 {
return "", errors.ErrNotFound
}
return srs[0].ReceiverType, nil
}
func (srm *SSubscriptionReceiverManager) findReceivers(ssid string, receiverTypes ...string) ([]SSubscriptionReceiver, error) {
if len(receiverTypes) == 0 {
return nil, nil
}
q := srm.Query().Equals("subscription_id", ssid)
if len(receiverTypes) == 1 {
q = q.Equals("receiver_type", receiverTypes[0])
} else {
q = q.In("receiver_type", receiverTypes)
}
srs := make([]SSubscriptionReceiver, 0)
err := db.FetchModelObjects(srm, q, &srs)
if err != nil {
return nil, errors.Wrap(err, "unable to FetchModelObjects")
}
return srs, nil
}
// TODO: Use cache to increase speed
func (srm *SSubscriptionReceiverManager) getReceivers(ctx context.Context, ssid string, projectDomainId string, projectId string) ([]string, error) {
srs, err := srm.findReceivers(ssid, ReceiverRole, ReceiverNormal)
if err != nil {
return nil, err
}
receivers := make([]string, 0, len(srs))
roleMap := make(map[string][]string, 3)
receivermap := make(map[string]*[]string, 3)
for _, sr := range srs {
if sr.ReceiverType == ReceiverNormal {
receivers = append(receivers, sr.Receiver)
} else if sr.ReceiverType == ReceiverRole {
roleMap[sr.RoleScope] = append(roleMap[sr.RoleScope], sr.Receiver)
receivermap[sr.RoleScope] = &[]string{}
}
}
errgo, _ := errgroup.WithContext(ctx)
for scope, roles := range roleMap {
receivers := receivermap[scope]
errgo.Go(func() error {
query := jsonutils.NewDict()
query.Set("roles", jsonutils.NewStringArray(roles))
query.Set("effective", jsonutils.JSONTrue)
switch scope {
case ScopeSystem:
case ScopeDomain:
if len(projectDomainId) == 0 {
return fmt.Errorf("need projectDomainId")
}
query.Set("project_domain_id", jsonutils.NewString(projectDomainId))
case ScopeProject:
if len(projectId) == 0 {
return fmt.Errorf("need projectId")
}
query.Add(jsonutils.NewString(projectId), "scope", "project", "id")
}
s := auth.GetAdminSession(ctx, "", "")
log.Debugf("query for role-assignments: %s", query.String())
listRet, err := modules.RoleAssignments.List(s, query)
if err != nil {
return errors.Wrap(err, "unable to list RoleAssignments")
}
log.Debugf("return value for role-assignments: %s", jsonutils.Marshal(listRet))
for i := range listRet.Data {
ras := listRet.Data[i]
user, err := ras.Get("user")
if err == nil {
id, err := user.GetString("id")
if err != nil {
return errors.Wrap(err, "unable to get user.id from result of RoleAssignments.List")
}
*receivers = append(*receivers, id)
}
}
return nil
})
}
err = errgo.Wait()
if err != nil {
return nil, err
}
for _, res := range receivermap {
receivers = append(receivers, *res...)
}
return receivers, nil
}
func (srm *SSubscriptionReceiverManager) create(ctx context.Context, ssid, receiver, receiverType, roleScope string) (*SSubscriptionReceiver, error) {
sr := &SSubscriptionReceiver{
SubscriptionID: ssid,
Receiver: receiver,
ReceiverType: receiverType,
RoleScope: roleScope,
}
err := srm.TableSpec().Insert(ctx, sr)
if err != nil {
return nil, errors.Wrap(err, "unable to insert")
}
return sr, nil
}
func (sr *SSubscriptionReceiver) receivingRole() notify.ReceivingRole {
return notify.ReceivingRole{
Role: sr.Receiver,
Scope: sr.RoleScope,
}
}
+403
View File
@@ -0,0 +1,403 @@
// 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"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/sets"
"yunion.io/x/sqlchemy"
"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/util/bitmap"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
func parseEvent(es string) (notify.SEvent, error) {
ess := strings.Split(es, notify.DelimiterInEvent)
if len(ess) != 2 {
return notify.SEvent{}, fmt.Errorf("invalid event string %q", es)
}
return notify.Event.WithResourceType(ess[0]).WithAction(notify.SAction(ess[1])), nil
}
type STopicManager struct {
db.SStandaloneResourceBaseManager
db.SEnabledResourceBaseManager
}
var TopicManager *STopicManager
func init() {
TopicManager = &STopicManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
STopic{},
"topic_tbl",
"topic",
"topics",
),
}
TopicManager.SetVirtualObject(TopicManager)
}
type STopic struct {
db.SStandaloneResourceBase
db.SEnabledResourceBase
Type string `width:"20" nullable:"false" create:"required" update:"user" list:"user"`
Resources uint64 `nullable:"false"`
Actions uint32 `nullable:"false"`
AdvanceDays int `nullable:"false"`
}
const (
DefaultResourceCreateDelete = "resource create or delete"
DefaultResourceChangeConfig = "resource change config"
DefaultResourceUpdate = "resource update"
DefaultResourceReleaseDue1Day = "resource release due 1 day"
DefaultResourceReleaseDue3Day = "resource release due 3 day"
DefaultScheduledTaskExecute = "scheduled task execute"
DefaultScalingPolicyExecute = "scaling policy execute"
DefaultSnapshotPolicyExecute = "snapshot policy execute"
)
func (sm *STopicManager) InitializeData() error {
initSNames := sets.NewString(
DefaultResourceCreateDelete,
DefaultResourceChangeConfig,
DefaultResourceUpdate,
DefaultResourceReleaseDue1Day,
DefaultResourceReleaseDue3Day,
DefaultScheduledTaskExecute,
DefaultScalingPolicyExecute,
DefaultSnapshotPolicyExecute,
)
q := sm.Query()
topics := make([]STopic, 0, initSNames.Len())
err := db.FetchModelObjects(sm, q, &topics)
if err != nil {
return errors.Wrap(err, "unable to FetchModelObjects")
}
for i := range topics {
t := &topics[i]
initSNames.Delete(t.Name)
}
ctx := context.Background()
for _, name := range initSNames.UnsortedList() {
t := new(STopic)
t.Name = name
t.Enabled = tristate.True
switch name {
case DefaultResourceCreateDelete:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
notify.TOPIC_RESOURCE_SCALINGGROUP,
notify.TOPIC_RESOURCE_IMAGE,
notify.TOPIC_RESOURCE_DISK,
notify.TOPIC_RESOURCE_SNAPSHOT,
notify.TOPIC_RESOURCE_INSTANCESNAPSHOT,
notify.TOPIC_RESOURCE_SNAPSHOTPOLICY,
notify.TOPIC_RESOURCE_NETWORK,
notify.TOPIC_RESOURCE_EIP,
notify.TOPIC_RESOURCE_LOADBALANCER,
notify.TOPIC_RESOURCE_LOADBALANCERACL,
notify.TOPIC_RESOURCE_LOADBALANCERCERTIFICATE,
notify.TOPIC_RESOURCE_BUCKET,
notify.TOPIC_RESOURCE_DBINSTANCE,
notify.TOPIC_RESOURCE_ELASTICCACHE,
)
t.addAction(
notify.ActionCreate,
notify.ActionDelete,
notify.ActionPendingDelete,
)
t.Type = notify.TOPIC_TYPE_RESOURCE
case DefaultResourceChangeConfig:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
notify.TOPIC_RESOURCE_DISK,
notify.TOPIC_RESOURCE_DBINSTANCE,
notify.TOPIC_RESOURCE_ELASTICCACHE,
)
t.addAction(notify.ActionChangeConfig)
t.Type = notify.TOPIC_TYPE_RESOURCE
case DefaultResourceUpdate:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
notify.TOPIC_RESOURCE_DISK,
notify.TOPIC_RESOURCE_DBINSTANCE,
notify.TOPIC_RESOURCE_ELASTICCACHE,
)
t.addAction(notify.ActionUpdate)
t.Type = notify.TOPIC_TYPE_RESOURCE
case DefaultResourceReleaseDue1Day:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
notify.TOPIC_RESOURCE_DISK,
notify.TOPIC_RESOURCE_EIP,
notify.TOPIC_RESOURCE_LOADBALANCER,
notify.TOPIC_RESOURCE_DBINSTANCE,
notify.TOPIC_RESOURCE_ELASTICCACHE,
)
t.addAction(notify.ActionExpiredRelease)
t.Type = notify.TOPIC_TYPE_RESOURCE
t.AdvanceDays = 1
case DefaultResourceReleaseDue3Day:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
notify.TOPIC_RESOURCE_DISK,
notify.TOPIC_RESOURCE_EIP,
notify.TOPIC_RESOURCE_LOADBALANCER,
notify.TOPIC_RESOURCE_DBINSTANCE,
notify.TOPIC_RESOURCE_ELASTICCACHE,
)
t.addAction(notify.ActionExpiredRelease)
t.Type = notify.TOPIC_TYPE_RESOURCE
t.AdvanceDays = 3
case DefaultScheduledTaskExecute:
t.addResources(notify.TOPIC_RESOURCE_SCHEDULEDTASK)
t.addAction(notify.ActionExecute)
t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS
case DefaultScalingPolicyExecute:
t.addResources(notify.TOPIC_RESOURCE_SCALINGPOLICY)
t.addAction(notify.ActionExecute)
t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS
case DefaultSnapshotPolicyExecute:
t.addResources(notify.TOPIC_RESOURCE_SNAPSHOTPOLICY)
t.addAction(notify.ActionExecute)
t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS
}
err := sm.TableSpec().Insert(ctx, t)
if err != nil {
return errors.Wrapf(err, "unable to insert %s", name)
}
}
return nil
}
func (sm *STopicManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input notify.TopicListInput) (*sqlchemy.SQuery, error) {
return sm.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput)
}
func (sm *STopicManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []notify.TopicDetails {
sRows := sm.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
rows := make([]notify.TopicDetails, len(objs))
for i := range rows {
rows[i].StandaloneResourceDetails = sRows[i]
ss := objs[i].(*STopic)
rows[i].Resources = ss.getResources()
}
return rows
}
type SSubscriberDis struct {
SSubscriber
ReceiverName string `json:"receiver_name"`
RoleName string `json:"role_name"`
}
func (s *STopic) subscriptionReceiverDiss() ([]SSubscriberDis, error) {
q := SubscriberManager.Query().Equals("subscription_id", s.Id)
rq := ReceiverManager.Query("id", "name").SubQuery()
roq := db.RoleCacheManager.Query("id", "name").SubQuery()
q = q.LeftJoin(rq, sqlchemy.Equals(q.Field("receiver"), rq.Field("id")))
q = q.LeftJoin(roq, sqlchemy.Equals(q.Field("receiver"), roq.Field("id")))
// It looks strange, but the order of append cannot be changed
q.AppendField(q.QueryFields()...)
q.AppendField(rq.Field("name", "receiver_name"))
q.AppendField(roq.Field("name", "role_name"))
srs := make([]SSubscriberDis, 0)
err := q.All(&srs)
if err != nil {
return nil, errors.Wrap(err, "unable to fetch All")
}
return srs, nil
}
func (sm *STopicManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return nil, httperrors.NewForbiddenError("prohibit creation")
}
func (ss *STopic) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return input, httperrors.NewForbiddenError("update prohibited")
}
func (ss *STopic) ValidateDeleteCondition(ctx context.Context) error {
return httperrors.NewForbiddenError("prohibit deletion")
}
func (s *STopic) addResources(resources ...string) {
for _, resource := range resources {
v := converter.resourceValue(resource)
if v < 0 {
continue
}
s.Resources += 1 << v
}
}
func (s *STopic) addAction(actions ...notify.SAction) {
for _, action := range actions {
v := converter.actionValue(action)
if v < 0 {
continue
}
s.Actions += 1 << v
}
}
func (s *STopic) getResources() []string {
vs := bitmap.Uint64ToIntArray(s.Resources)
resources := make([]string, 0, len(vs))
for _, v := range vs {
resources = append(resources, converter.resource(v))
}
return resources
}
func (s *STopic) getActions() []notify.SAction {
vs := bitmap.Uint2IntArray(s.Actions)
actions := make([]notify.SAction, 0, len(vs))
for _, v := range vs {
actions = append(actions, converter.action(v))
}
return actions
}
func (sm *STopicManager) TopicsByEvent(eventStr string, advanceDays int) ([]STopic, error) {
event, err := parseEvent(eventStr)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse event %q", event)
}
resourceV := converter.resourceValue(event.ResourceType())
actionV := converter.actionValue(event.Action())
q := sm.Query().Equals("advance_days", advanceDays)
q = q.Filter(sqlchemy.GT(sqlchemy.AND_Val("", q.Field("resources"), 1<<resourceV), 0))
q = q.Filter(sqlchemy.GT(sqlchemy.AND_Val("", q.Field("actions"), 1<<actionV), 0))
var topics []STopic
err = db.FetchModelObjects(sm, q, &topics)
if err != nil {
q.DebugQuery()
return nil, errors.Wrap(err, "unable to FetchModelObjects")
}
return topics, nil
}
func init() {
converter = &sConverter{
resourceValueMap: make(map[string]int, 5),
resourceList: make([]string, 0, 5),
actionList: make([]notify.SAction, 0, 5),
actionValueMap: make(map[notify.SAction]int, 5),
}
converter.registerResource(
notify.TOPIC_RESOURCE_SERVER,
notify.TOPIC_RESOURCE_SCALINGGROUP,
notify.TOPIC_RESOURCE_SCALINGPOLICY,
notify.TOPIC_RESOURCE_IMAGE,
notify.TOPIC_RESOURCE_DISK,
notify.TOPIC_RESOURCE_SNAPSHOT,
notify.TOPIC_RESOURCE_INSTANCESNAPSHOT,
notify.TOPIC_RESOURCE_SNAPSHOTPOLICY,
notify.TOPIC_RESOURCE_NETWORK,
notify.TOPIC_RESOURCE_EIP,
notify.TOPIC_RESOURCE_SECGROUP,
notify.TOPIC_RESOURCE_LOADBALANCER,
notify.TOPIC_RESOURCE_LOADBALANCERACL,
notify.TOPIC_RESOURCE_LOADBALANCERCERTIFICATE,
notify.TOPIC_RESOURCE_BUCKET,
notify.TOPIC_RESOURCE_DBINSTANCE,
notify.TOPIC_RESOURCE_ELASTICCACHE,
notify.TOPIC_RESOURCE_SCHEDULEDTASK,
)
converter.registerAction(
notify.ActionCreate,
notify.ActionDelete,
notify.ActionPendingDelete,
notify.ActionUpdate,
notify.ActionRebuildRoot,
notify.ActionResetPassword,
notify.ActionChangeConfig,
notify.ActionExpiredRelease,
notify.ActionExecute,
)
}
var converter *sConverter
type sConverter struct {
resourceValueMap map[string]int
resourceList []string
actionValueMap map[notify.SAction]int
actionList []notify.SAction
}
func (rc *sConverter) registerResource(resources ...string) {
for _, resource := range resources {
if _, ok := rc.resourceValueMap[resource]; ok {
return
}
rc.resourceList = append(rc.resourceList, resource)
rc.resourceValueMap[resource] = len(rc.resourceList) - 1
}
}
func (rc *sConverter) registerAction(actions ...notify.SAction) {
for _, action := range actions {
if _, ok := rc.actionValueMap[action]; ok {
return
}
rc.actionList = append(rc.actionList, action)
rc.actionValueMap[action] = len(rc.actionList) - 1
}
}
func (rc *sConverter) resourceValue(resource string) int {
v, ok := rc.resourceValueMap[resource]
if !ok {
return -1
}
return v
}
func (rc *sConverter) resource(resourceValue int) string {
if resourceValue < 0 || resourceValue >= len(rc.resourceList) {
return ""
}
return rc.resourceList[resourceValue]
}
func (rc *sConverter) actionValue(action notify.SAction) int {
v, ok := rc.actionValueMap[action]
if !ok {
return -1
}
return v
}
func (rc *sConverter) action(actionValue int) notify.SAction {
if actionValue < 0 || actionValue >= len(rc.actionList) {
return notify.SAction("")
}
return rc.actionList[actionValue]
}
+2 -2
View File
@@ -39,7 +39,6 @@ func InitHandlers(app *appsrv.Application) {
models.SubContactManager,
db.SharedResourceManager,
models.VerificationManager,
models.SubscriptionReceiverManager,
} {
db.RegisterModelManager(manager)
}
@@ -51,8 +50,9 @@ func InitHandlers(app *appsrv.Application) {
models.NotificationManager,
models.ConfigManager,
models.TemplateManager,
models.SubscriptionManager,
models.TopicManager,
models.RobotManager,
models.SubscriberManager,
} {
db.RegisterModelManager(manager)
handler := db.NewModelHandler(manager)