feishu.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package notifiers
  15. import (
  16. "fmt"
  17. "golang.org/x/sync/errgroup"
  18. "yunion.io/x/jsonutils"
  19. "yunion.io/x/log"
  20. "yunion.io/x/pkg/errors"
  21. "yunion.io/x/onecloud/pkg/apis/monitor"
  22. "yunion.io/x/onecloud/pkg/httperrors"
  23. "yunion.io/x/onecloud/pkg/mcclient"
  24. "yunion.io/x/onecloud/pkg/monitor/alerting"
  25. "yunion.io/x/onecloud/pkg/monitor/notifydrivers/feishu"
  26. )
  27. func init() {
  28. alerting.RegisterNotifier(&alerting.NotifierPlugin{
  29. Type: monitor.AlertNotificationTypeFeishu,
  30. Factory: newFeishuNotifier,
  31. ValidateCreateData: func(cred mcclient.IIdentityProvider, input monitor.NotificationCreateInput) (monitor.NotificationCreateInput, error) {
  32. settings := new(monitor.NotificationSettingFeishu)
  33. if err := input.Settings.Unmarshal(settings); err != nil {
  34. return input, errors.Wrap(err, "unmarshal setting")
  35. }
  36. if settings.AppId == "" {
  37. return input, httperrors.NewInputParameterError("app_id is empty")
  38. }
  39. if settings.AppSecret == "" {
  40. return input, httperrors.NewInputParameterError("app_secret is empty")
  41. }
  42. _, err := feishu.NewTenant(settings.AppId, settings.AppSecret)
  43. if err != nil {
  44. return input, httperrors.NewGeneralError(errors.Wrap(err, "test connection"))
  45. }
  46. input.Settings = jsonutils.Marshal(settings)
  47. return input, nil
  48. },
  49. })
  50. }
  51. type FeishuNotifier struct {
  52. NotifierBase
  53. // Settings *monitor.NotificationSettingFeishu
  54. Client *feishu.Tenant
  55. ChatIds []string
  56. }
  57. func newFeishuNotifier(config alerting.NotificationConfig) (alerting.Notifier, error) {
  58. settings := new(monitor.NotificationSettingFeishu)
  59. if err := config.Settings.Unmarshal(settings); err != nil {
  60. return nil, errors.Wrap(err, "unmarshal setting")
  61. }
  62. cli, err := feishu.NewTenant(settings.AppId, settings.AppSecret)
  63. if err != nil {
  64. return nil, err
  65. }
  66. ret, err := cli.ChatList(0, "")
  67. if err != nil {
  68. return nil, err
  69. }
  70. chatIds := make([]string, 0)
  71. for _, obj := range ret.Data.Groups {
  72. chatIds = append(chatIds, obj.ChatId)
  73. }
  74. return &FeishuNotifier{
  75. NotifierBase: NewNotifierBase(config),
  76. Client: cli,
  77. ChatIds: chatIds,
  78. }, nil
  79. }
  80. func (fs *FeishuNotifier) Notify(ctx *alerting.EvalContext, _ jsonutils.JSONObject) error {
  81. log.Infof("Sending alert notification to feishu")
  82. errGrp := errgroup.Group{}
  83. for _, cId := range fs.ChatIds {
  84. errGrp.Go(func() error {
  85. msg, err := fs.genCard(ctx, cId)
  86. if err != nil {
  87. return err
  88. }
  89. if _, err := fs.Client.SendMessage(*msg); err != nil {
  90. log.Errorf("--feishu send msg error: %s, error: %v", jsonutils.Marshal(msg), err)
  91. return err
  92. }
  93. log.Errorf("--feishu send msg: %s", jsonutils.Marshal(msg))
  94. return nil
  95. })
  96. }
  97. return errGrp.Wait()
  98. }
  99. func (fs *FeishuNotifier) getCommonInfoMod(config monitor.NotificationTemplateConfig) feishu.CardElement {
  100. fields := []*feishu.CardElementField{
  101. feishu.NewCardElementTextField(false, fmt.Sprintf("**时间:** %s", config.StartTime)),
  102. feishu.NewCardElementTextField(false, fmt.Sprintf("**级别:** %s", config.Level)),
  103. }
  104. if config.Reason != "" {
  105. fields = append(fields, feishu.NewCardElementTextField(false, fmt.Sprintf("**原因:** %s", config.Reason)))
  106. }
  107. elem := feishu.CardElement{
  108. Tag: feishu.TagDiv,
  109. Fields: fields,
  110. }
  111. return elem
  112. }
  113. func (fs *FeishuNotifier) getMetricElem(idx int, m monitor.EvalMatch) *feishu.CardElement {
  114. var val string
  115. if m.Value == nil {
  116. val = "NaN"
  117. } else {
  118. val = fmt.Sprintf("%.2f", *m.Value)
  119. }
  120. elem := feishu.CardElement{
  121. Tag: feishu.TagDiv,
  122. Fields: []*feishu.CardElementField{
  123. feishu.NewCardElementTextField(false,
  124. fmt.Sprintf("**指标 %d:** %s", idx, m.Metric)),
  125. feishu.NewCardElementTextField(false,
  126. fmt.Sprintf("**当前值:** %s", val)),
  127. feishu.NewCardElementTextField(true,
  128. fmt.Sprintf("**触发条件:**\n%s", m.Condition)),
  129. },
  130. }
  131. return &elem
  132. }
  133. func (fs *FeishuNotifier) getMetricTagElem(m monitor.EvalMatch) *feishu.CardElement {
  134. inElems := make([]*feishu.CardElement, 0)
  135. for val, key := range m.Tags {
  136. inElems = append(inElems, feishu.NewCardElementText(fmt.Sprintf("%s: %s", val, key)))
  137. }
  138. elem := feishu.CardElement{
  139. Tag: feishu.TagNote,
  140. Elements: inElems,
  141. }
  142. return &elem
  143. }
  144. func (fs *FeishuNotifier) getMetricsMod(config monitor.NotificationTemplateConfig) []*feishu.CardElement {
  145. inElems := make([]*feishu.CardElement, 0)
  146. for idx, m := range config.Matches {
  147. hrE := feishu.NewCardElementHR()
  148. mE := fs.getMetricElem(idx+1, *m)
  149. mTE := fs.getMetricTagElem(*m)
  150. inElems = append(inElems, hrE, mE, mTE)
  151. }
  152. return inElems
  153. }
  154. func (fs *FeishuNotifier) genCard(ctx *alerting.EvalContext, chatId string) (*feishu.MsgReq, error) {
  155. config := GetNotifyTemplateConfig(ctx, false, ctx.EvalMatches)
  156. commonElem := fs.getCommonInfoMod(config)
  157. msElems := fs.getMetricsMod(config)
  158. // 消息卡片: https://open.feishu.cn/document/ukTMukTMukTM/uYTNwUjL2UDM14iN1ATN
  159. msg := &feishu.MsgReq{
  160. ChatId: chatId,
  161. MsgType: feishu.MsgTypeInteractive,
  162. Card: &feishu.Card{
  163. Config: &feishu.CardConfig{WideScreenMode: false},
  164. CardLink: nil,
  165. Header: &feishu.CardHeader{
  166. Title: &feishu.CardHeaderTitle{
  167. Tag: feishu.TagPlainText,
  168. Content: config.Title,
  169. },
  170. },
  171. Elements: []interface{}{
  172. commonElem,
  173. },
  174. },
  175. }
  176. for _, elem := range msElems {
  177. msg.Card.Elements = append(msg.Card.Elements, elem)
  178. }
  179. return msg, nil
  180. }
  181. func (fs *FeishuNotifier) genMsg(ctx *alerting.EvalContext, chatId string) (*feishu.MsgReq, error) {
  182. config := GetNotifyTemplateConfig(ctx, false, ctx.EvalMatches)
  183. // 富文本: https://open.feishu.cn/document/ukTMukTMukTM/uMDMxEjLzATMx4yMwETM
  184. return &feishu.MsgReq{
  185. ChatId: chatId,
  186. MsgType: feishu.MsgTypePost,
  187. Content: &feishu.MsgContent{
  188. Post: &feishu.MsgPost{
  189. ZhCn: &feishu.MsgPostValue{
  190. Title: config.Title,
  191. Content: []interface{}{
  192. []interface{}{
  193. feishu.MsgPostContentText{
  194. Tag: "text",
  195. UnEscape: true,
  196. Text: "first line",
  197. },
  198. },
  199. },
  200. },
  201. },
  202. },
  203. }, nil
  204. }