| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- // 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"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/rbacscope"
- "yunion.io/x/pkg/util/sets"
- "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/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/notify/options"
- "yunion.io/x/onecloud/pkg/util/logclient"
- "yunion.io/x/onecloud/pkg/util/stringutils2"
- )
- type SConfigManager struct {
- db.SDomainLevelResourceBaseManager
- }
- var ConfigManager *SConfigManager
- var ConfigMap map[string]SConfig
- func init() {
- ConfigManager = &SConfigManager{
- SDomainLevelResourceBaseManager: db.NewDomainLevelResourceBaseManager(
- SConfig{},
- "configs_tbl",
- "notifyconfig",
- "notifyconfigs",
- ),
- }
- ConfigManager.SetVirtualObject(ConfigManager)
- }
- type SConfig struct {
- db.SDomainLevelResourceBase
- Type string `width:"15" nullable:"false" create:"required" get:"domain" list:"domain" index:"true"`
- Content *api.SNotifyConfigContent `nullable:"false" create:"required" update:"domain" get:"domain" list:"domain"`
- Attribution string `width:"8" nullable:"false" default:"system" get:"domain" list:"domain" create:"optional"`
- }
- 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.DomainLevelResourceCreateInput, err = cm.SDomainLevelResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.DomainLevelResourceCreateInput)
- if err != nil {
- return input, err
- }
- if !utils.IsInStringArray(input.Type, GetSenderTypes()) {
- return input, httperrors.NewInputParameterError("unkown type %s allow: %s", input.Type, GetSenderTypes())
- }
- if !utils.IsInStringArray(input.Attribution, []string{api.CONFIG_ATTRIBUTION_SYSTEM, api.CONFIG_ATTRIBUTION_DOMAIN}) {
- return input, httperrors.NewInputParameterError("invalid attribution, need %q or %q", api.CONFIG_ATTRIBUTION_SYSTEM, api.CONFIG_ATTRIBUTION_DOMAIN)
- }
- if input.Content == nil {
- return input, httperrors.NewMissingParameterError("content")
- }
- config, err := cm.Config(input.Type, input.ProjectDomainId, input.Attribution)
- if config != nil {
- return input, httperrors.NewDuplicateResourceError("duplicate type %q", input.Type)
- }
- if err != nil && errors.Cause(err) != sql.ErrNoRows {
- return input, err
- }
- driver := GetDriver(input.Type)
- // validate
- if input.Type != api.MOBILE {
- message, err := driver.ValidateConfig(ctx, api.NotifyConfig{
- SNotifyConfigContent: *input.Content,
- Attribution: input.Attribution,
- DomainId: input.ProjectDomainId,
- })
- if err != nil {
- return input, errors.Wrapf(err, "%s", message)
- }
- }
- if len(input.Name) == 0 {
- input.Name = input.Type
- }
- return input, nil
- }
- func (manager *SConfigManager) GetPropertyCapability(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- q := manager.Query()
- configs := []SConfig{}
- err := db.FetchModelObjects(manager, q, &configs)
- if err != nil {
- return nil, err
- }
- ret := struct {
- System []string `json:"system,allowempty"`
- Domain map[string][]string `json:"domain,allowempty"`
- }{
- System: []string{},
- Domain: map[string][]string{},
- }
- for _, config := range configs {
- switch config.Attribution {
- case api.CONFIG_ATTRIBUTION_SYSTEM:
- ret.System = append(ret.System, config.Type)
- case api.CONFIG_ATTRIBUTION_DOMAIN:
- _, ok := ret.Domain[config.DomainId]
- if !ok {
- ret.Domain[config.DomainId] = []string{}
- }
- ret.Domain[config.DomainId] = append(ret.Domain[config.DomainId], config.Type)
- }
- }
- return jsonutils.Marshal(ret), nil
- }
- func (c *SConfig) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
- c.SDomainLevelResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
- err := c.StartRepullSubcontactTask(ctx, userCred, false)
- if err != nil {
- log.Errorf("unable to StartRepullSubcontactTask: %v", err)
- }
- driver := GetDriver(c.Type)
- driver.RegisterConfig(*c)
- }
- func (c *SConfig) GetNotifyConfig() api.NotifyConfig {
- return api.NotifyConfig{
- SNotifyConfigContent: *c.Content,
- Attribution: c.Attribution,
- DomainId: c.DomainId,
- }
- }
- func (c *SConfig) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ConfigUpdateInput) (api.ConfigUpdateInput, error) {
- q := ConfigManager.Query()
- q = q.Equals("type", c.Type)
- confs := []SConfig{}
- err := db.FetchModelObjects(ConfigManager, q, &confs)
- if err != nil {
- return input, errors.Wrapf(err, "config type:%s", c.Type)
- }
- if len(confs) == 0 {
- return input, errors.Wrapf(errors.ErrNotFound, "config type:%s", c.Type)
- }
- // check if changed
- if input.Content != nil {
- driver := GetDriver(c.Type)
- if c.Type != api.MOBILE {
- message, err := driver.ValidateConfig(ctx, api.NotifyConfig{
- SNotifyConfigContent: *input.Content,
- Attribution: c.Attribution,
- DomainId: c.DomainId,
- })
- if err != nil {
- return input, errors.Wrapf(err, "%s", message)
- }
- }
- }
- return input, nil
- }
- func (c *SConfig) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
- c.SStandaloneResourceBase.PostUpdate(ctx, userCred, query, data)
- config := c.GetNotifyConfig()
- ConfigMap[fmt.Sprintf("%s-%s", c.Type, c.DomainId)] = SConfig{
- Content: &config.SNotifyConfigContent,
- }
- err := c.StartRepullSubcontactTask(ctx, userCred, false)
- if err != nil {
- log.Errorf("unable to StartRepullSubcontactTask: %v", err)
- }
- }
- func (c *SConfig) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) {
- c.SStandaloneResourceBase.PreDelete(ctx, userCred)
- key := fmt.Sprintf("%s-%s", c.Type, c.DomainId)
- if c.Type == api.MOBILE || c.Type == api.EMAIL {
- key = c.Type
- }
- delete(ConfigMap, key)
- }
- func (c *SConfig) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
- err := c.SStandaloneResourceBase.CustomizeDelete(ctx, userCred, query, data)
- if err != nil {
- return err
- }
- return c.StartRepullSubcontactTask(ctx, userCred, true)
- }
- func (c *SConfig) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
- return nil
- }
- func (c *SConfig) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
- delete(ConfigMap, fmt.Sprintf("%s-%s", c.Type, c.DomainId))
- return c.SDomainLevelResourceBase.Delete(ctx, userCred)
- }
- func (self *SConfig) GetReceivers() ([]SReceiver, error) {
- subq := SubContactManager.Query("receiver_id").Equals("type", self.Type).SubQuery()
- q := ReceiverManager.Query()
- if self.Attribution == api.CONFIG_ATTRIBUTION_DOMAIN {
- q = q.Equals("domain_id", self.DomainId)
- } else {
- // The system-level config update should not affect the receiver under the domain with config
- configq := ConfigManager.Query("domain_id").Equals("type", self.Type).Equals("attribution", api.CONFIG_ATTRIBUTION_DOMAIN).SubQuery()
- q = q.NotIn("domain_id", configq)
- }
- q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("receiver_id")))
- ret := []SReceiver{}
- err := db.FetchModelObjects(ReceiverManager, q, &ret)
- return ret, err
- }
- func (c *SConfig) StartRepullSubcontactTask(ctx context.Context, userCred mcclient.TokenCredential, del bool) error {
- taskData := jsonutils.NewDict()
- if del {
- taskData.Set("deleted", jsonutils.JSONTrue)
- }
- task, err := taskman.TaskManager.NewTask(ctx, "RepullSuncontactTask", c, userCred, taskData, "", "")
- if err != nil {
- return err
- }
- task.ScheduleRun(nil)
- return nil
- }
- var sortedCTypes = []string{
- api.WEBCONSOLE, api.EMAIL, api.MOBILE, api.DINGTALK, api.FEISHU, api.WORKWX,
- }
- func sortContactType(ctypes []string) []string {
- ctSet := sets.NewString(ctypes...)
- ret := make([]string, 0, len(ctypes))
- for _, ct := range sortedCTypes {
- if ctSet.Has(ct) {
- ret = append(ret, ct)
- }
- }
- return ret
- }
- func (cm *SConfigManager) contactTypesQuery(domainId string) *sqlchemy.SQuery {
- q := cm.Query("type").Distinct()
- if domainId == "" {
- q = q.Equals("attribution", api.CONFIG_ATTRIBUTION_SYSTEM)
- } else {
- q = q.Filter(sqlchemy.OR(sqlchemy.AND(sqlchemy.Equals(q.Field("attribution"), api.CONFIG_ATTRIBUTION_DOMAIN), sqlchemy.Equals(q.Field("domain_id"), domainId)), sqlchemy.Equals(q.Field("attribution"), api.CONFIG_ATTRIBUTION_SYSTEM)))
- }
- return q
- }
- func (cm *SConfigManager) availableContactTypes(domainId string) ([]string, error) {
- q := cm.Query("type")
- q = q.Filter(sqlchemy.OR(sqlchemy.AND(sqlchemy.Equals(q.Field("attribution"), api.CONFIG_ATTRIBUTION_DOMAIN), sqlchemy.Equals(q.Field("domain_id"), domainId)), sqlchemy.Equals(q.Field("attribution"), api.CONFIG_ATTRIBUTION_SYSTEM)))
- allTypes := make([]struct {
- Type string
- }, 0, 3)
- err := q.All(&allTypes)
- if err != nil {
- return nil, err
- }
- ret := make([]string, len(allTypes))
- for i := range ret {
- ret[i] = allTypes[i].Type
- }
- // De-duplication
- return sets.NewString(ret...).UnsortedList(), nil
- }
- func (cm *SConfigManager) allContactType(domainid string) ([]string, error) {
- q := cm.Query("type")
- q = q.Equals("domain_id", domainid)
- allTypes := make([]struct {
- Type string
- }, 0, 3)
- err := q.All(&allTypes)
- if err != nil {
- return nil, err
- }
- ret := make([]string, len(allTypes))
- for i := range ret {
- ret[i] = allTypes[i].Type
- }
- return ret, nil
- }
- func (self *SConfigManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.ConfigListInput) (*sqlchemy.SQuery, error) {
- q, err := self.SDomainLevelResourceBaseManager.ListItemFilter(ctx, q, userCred, input.DomainLevelResourceListInput)
- 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))
- }
- if len(input.Attribution) > 0 {
- q = q.Equals("attribution", input.Attribution)
- }
- return q, nil
- }
- func (manager *SConfigManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, keys stringutils2.SSortedStrings) (*sqlchemy.SQuery, error) {
- return manager.SDomainLevelResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
- }
- func (cm *SConfigManager) FetchCustomizeColumns(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- objs []interface{},
- fields stringutils2.SSortedStrings,
- isList bool,
- ) []api.ConfigDetails {
- sRows := cm.SDomainLevelResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
- rows := make([]api.ConfigDetails, len(objs))
- for i := range rows {
- rows[i].DomainLevelResourceDetails = sRows[i]
- }
- return rows
- }
- func (cm *SConfigManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
- q, err := cm.SDomainLevelResourceBaseManager.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.SDomainLevelResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.DomainLevelResourceListInput)
- if err != nil {
- return nil, err
- }
- return q, nil
- }
- func (cm *SConfigManager) PerformValidate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ConfigValidateInput) (api.ConfigValidateOutput, error) {
- output := api.ConfigValidateOutput{}
- if !utils.IsInStringArray(input.Type, GetSenderTypes()) {
- return output, httperrors.NewInputParameterError("unkown type %q", input.Type)
- }
- if input.Content == nil {
- return output, httperrors.NewMissingParameterError("content")
- }
- // validate
- driver := GetDriver(input.Type)
- message, err := driver.ValidateConfig(ctx, api.NotifyConfig{
- SNotifyConfigContent: *input.Content,
- DomainId: userCred.GetDomainId(),
- })
- if err != nil {
- return output, errors.Wrapf(err, "%s", message)
- }
- output.IsValid = true
- output.Message = message
- return output, nil
- }
- func (confManager *SConfigManager) InitializeData() error {
- q := confManager.Query()
- res := []SConfig{}
- err := db.FetchModelObjects(confManager, q, &res)
- if err != nil {
- return errors.Wrap(err, "init configMap err")
- }
- ConfigMap = make(map[string]SConfig)
- for _, config := range res {
- driver := GetDriver(config.Type)
- driver.RegisterConfig(config)
- err := driver.GetAccessToken(context.Background(), config.DomainId)
- if err != nil {
- session := auth.GetAdminSession(context.Background(), options.Options.Region)
- logclient.AddSimpleActionLog(&config, logclient.ACT_INIT_NOTIFY_CONFIGMAP, err, session.GetToken(), false)
- }
- }
- log.Infoln("init ConfigMap:", jsonutils.Marshal(ConfigMap))
- return nil
- }
- func (cm *SConfigManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
- switch scope {
- case rbacscope.ScopeDomain, rbacscope.ScopeProject:
- q = q.Equals("attribution", api.CONFIG_ATTRIBUTION_DOMAIN)
- if owner != nil {
- q = q.Equals("domain_id", owner.GetProjectDomainId())
- }
- }
- return q
- }
- // Fetch all SConfig struct which type is contactType.
- func (self *SConfigManager) Configs(contactType string) ([]SConfig, error) {
- var configs = make([]SConfig, 0, 2)
- q := self.Query()
- q.Filter(sqlchemy.Equals(q.Field("type"), contactType))
- err := q.All(&configs)
- if err != nil {
- return nil, errors.Wrapf(err, "fail to fetch SConfigs by type %s", contactType)
- }
- return configs, nil
- }
- func (self *SConfigManager) Config(contactType, domainId string, attribution string) (*SConfig, error) {
- q := self.Query()
- q = q.Equals("type", contactType).Equals("attribution", attribution)
- if attribution == api.CONFIG_ATTRIBUTION_DOMAIN {
- q = q.Equals("domain_id", domainId)
- }
- var config SConfig
- err := q.First(&config)
- if err != nil {
- return nil, errors.Wrapf(err, "fail to fetch SConfig by type %s and domain %s", contactType, domainId)
- }
- return &config, nil
- }
- func (self *SConfigManager) HasSystemConfig(contactType string) (bool, error) {
- q := self.Query().Equals("type", contactType).Equals("attribution", api.CONFIG_ATTRIBUTION_SYSTEM)
- c, err := q.CountWithError()
- if err != nil {
- return false, err
- }
- return c > 0, nil
- }
|