├── cmd
└── alertmanager-webhook-adapter
│ ├── .gitignore
│ ├── main.go
│ └── app
│ ├── rootcmd.go
│ └── options
│ └── options.go
├── docs
├── feishu_en.png
├── feishu_zh.png
├── slack_en.png
├── slack_zh.png
├── weixin_en.png
├── weixin_zh.png
├── dingtalk_en.png
├── dingtalk_zh.png
├── feishu_en_2.png
├── feishu_zh_2.png
├── weixinapp_en.png
├── weixinapp_zh.png
└── screenshot-zh.md
├── pkg
├── version
│ └── version.go
├── senders
│ ├── all.go
│ ├── feishu.go
│ ├── weixin.go
│ ├── dingtalk.go
│ ├── discord.go
│ ├── slack.go
│ └── weixinapp.go
├── models
│ ├── templates
│ │ ├── README.md
│ │ ├── templates.go
│ │ ├── default.zh.tmpl
│ │ ├── slack.zh.tmpl
│ │ ├── default.tmpl
│ │ ├── discord-webhook.tmpl
│ │ ├── dingtalk.zh.tmpl
│ │ ├── slack.tmpl
│ │ ├── dingtalk.tmpl
│ │ ├── feishu.zh.tmpl
│ │ ├── weixinapp.zh.tmpl
│ │ ├── weixin.zh.tmpl
│ │ ├── feishu.tmpl
│ │ ├── weixin.tmpl
│ │ └── weixinapp.tmpl
│ ├── kv.go
│ ├── alert.go
│ └── tmpl.go
└── api
│ └── api.go
├── deploy
├── charts
│ └── alertmanager-webhook-adapter
│ │ ├── values.yaml
│ │ ├── Chart.yaml
│ │ ├── artifacthub-repo.yml
│ │ └── templates
│ │ ├── service.yaml
│ │ ├── _helpers.tpl
│ │ └── deployment.yaml
├── k8s
│ ├── service.yaml
│ └── deployment.yaml
├── alertmanaget-webhook-adapter.service
└── alertmanager-webhook-adapter.init.sh
├── Dockerfile
├── .dockerignore
├── .gitignore
├── .github
└── workflows
│ ├── release-binary.yaml
│ └── release-docker.yaml
├── go.mod
├── tests
├── grafana-alert.json
├── alert-test.sh
└── alert.json
├── Makefile
├── LICENSE
├── README.md
└── go.sum
/cmd/alertmanager-webhook-adapter/.gitignore:
--------------------------------------------------------------------------------
1 | alertmanager-webhook-adapter
2 | templates
3 |
--------------------------------------------------------------------------------
/docs/feishu_en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/feishu_en.png
--------------------------------------------------------------------------------
/docs/feishu_zh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/feishu_zh.png
--------------------------------------------------------------------------------
/docs/slack_en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/slack_en.png
--------------------------------------------------------------------------------
/docs/slack_zh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/slack_zh.png
--------------------------------------------------------------------------------
/docs/weixin_en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/weixin_en.png
--------------------------------------------------------------------------------
/docs/weixin_zh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/weixin_zh.png
--------------------------------------------------------------------------------
/docs/dingtalk_en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/dingtalk_en.png
--------------------------------------------------------------------------------
/docs/dingtalk_zh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/dingtalk_zh.png
--------------------------------------------------------------------------------
/docs/feishu_en_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/feishu_en_2.png
--------------------------------------------------------------------------------
/docs/feishu_zh_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/feishu_zh_2.png
--------------------------------------------------------------------------------
/docs/weixinapp_en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/weixinapp_en.png
--------------------------------------------------------------------------------
/docs/weixinapp_zh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bougou/alertmanager-webhook-adapter/HEAD/docs/weixinapp_zh.png
--------------------------------------------------------------------------------
/pkg/version/version.go:
--------------------------------------------------------------------------------
1 | package version
2 |
3 | var (
4 | Version string
5 | Commit string
6 | BuildAt string
7 | )
8 |
--------------------------------------------------------------------------------
/deploy/charts/alertmanager-webhook-adapter/values.yaml:
--------------------------------------------------------------------------------
1 | image:
2 | name: bougou/alertmanager-webhook-adapter
3 | tag: v1.1.10
4 | signature: MyIDC
5 | lang: en
6 | timezone: Asia/Shanghai
7 |
--------------------------------------------------------------------------------
/deploy/charts/alertmanager-webhook-adapter/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | appVersion: 1.1.10
3 | description: helm chart for alertmanager webhook adapter
4 | name: alertmanager-webhook-adapter
5 | version: 1.0.1
6 | type: application
7 | maintainers:
8 | - name: bougou
9 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.18
2 |
3 | COPY / /src
4 |
5 | RUN cd /src \
6 | && make build \
7 | && mv /src/_output/alertmanager-webhook-adapter /alertmanager-webhook-adapter \
8 | && rm -rf /src \
9 | && true
10 |
11 | ENTRYPOINT [ "/alertmanager-webhook-adapter" ]
12 | CMD ["--debug"]
13 |
--------------------------------------------------------------------------------
/deploy/k8s/service.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | name: alertmanager-webhook-adapter
6 | namespace: infra
7 | spec:
8 | ports:
9 | - port: 80
10 | targetPort: 8090
11 | protocol: TCP
12 | selector:
13 | app: alertmanager-webhook-adapter
14 | sessionAffinity: None
15 |
--------------------------------------------------------------------------------
/deploy/charts/alertmanager-webhook-adapter/artifacthub-repo.yml:
--------------------------------------------------------------------------------
1 | # Artifact Hub repository metadata file
2 | #
3 | # ref: https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-repo.yml
4 |
5 | repositoryID: alertmanager-webhook-adapter
6 | owners:
7 | - name: bougou
8 | email: bougou@126.com
9 | ignore: []
10 |
--------------------------------------------------------------------------------
/deploy/alertmanaget-webhook-adapter.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=alertmanager-webhook-adapter
3 | Wants=network-online.target
4 | After=network-online.target
5 |
6 | [Service]
7 | ExecStart=/usr/local/bin/alertmanager-webhook-adapter \
8 | --listen-address=:8060
9 | Restart=always
10 |
11 | [Install]
12 | WantedBy=multi-user.target
13 |
--------------------------------------------------------------------------------
/deploy/charts/alertmanager-webhook-adapter/templates/service.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | name: {{ include "awa.fullname" . }}
6 | namespace: {{ include "awa.namespace" . }}
7 | spec:
8 | ports:
9 | - port: 80
10 | targetPort: 8090
11 | protocol: TCP
12 | selector:
13 | app: alertmanager-webhook-adapter
14 | sessionAffinity: None
15 |
--------------------------------------------------------------------------------
/pkg/senders/all.go:
--------------------------------------------------------------------------------
1 | package senders
2 |
3 | import (
4 | "github.com/bougou/webhook-adapter/models"
5 | restful "github.com/emicklei/go-restful/v3"
6 | )
7 |
8 | type ChannelSenderCreator func(request *restful.Request) (models.Sender, error)
9 |
10 | var ChannelsSenderCreatorMap = map[string]ChannelSenderCreator{}
11 |
12 | func RegisterChannelsSenderCreator(channel string, creator ChannelSenderCreator) {
13 | ChannelsSenderCreatorMap[channel] = creator
14 | }
15 |
--------------------------------------------------------------------------------
/cmd/alertmanager-webhook-adapter/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/bougou/alertmanager-webhook-adapter/cmd/alertmanager-webhook-adapter/app"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | func init() {
12 | cobra.OnInitialize(initConfig)
13 | }
14 |
15 | func initConfig() {
16 | }
17 |
18 | func main() {
19 | command := app.NewRootCommand()
20 | if err := command.Execute(); err != nil {
21 | fmt.Fprintln(os.Stderr, err)
22 | os.Exit(1)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Unit test / coverage reports
2 | htmlcov/
3 | .tox/
4 | .coverage
5 | .coverage.*
6 | .cache
7 | nosetests.xml
8 | coverage.xml
9 | *.cover
10 | .hypothesis/
11 | cover.out
12 | e2e-reports/
13 |
14 | # Translations
15 | *.mo
16 | *.pot
17 |
18 | # emacs
19 | .\#*
20 |
21 | # vim
22 | *.swp
23 |
24 | # ignore bin
25 | /bin/
26 | _output
27 | dist
28 |
29 | # ignore vscode
30 | .vscode
31 |
32 | # goland
33 | .idea
34 |
35 | # Test binary, build with `go test -c`
36 | *.test
37 |
38 | # Output of the go coverage tool, specifically when used with LiteIDE
39 | *.out
40 |
41 | .DS_Store
42 |
43 | _output
44 | build
45 | output
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Unit test / coverage reports
2 | htmlcov/
3 | .tox/
4 | .coverage
5 | .coverage.*
6 | .cache
7 | nosetests.xml
8 | coverage.xml
9 | *.cover
10 | .hypothesis/
11 | cover.out
12 | e2e-reports/
13 |
14 | # Translations
15 | *.mo
16 | *.pot
17 |
18 | # emacs
19 | .\#*
20 |
21 | # vim
22 | *.swp
23 |
24 | # ignore bin
25 | /bin/
26 | _output
27 | dist
28 |
29 | # ignore vscode
30 | .vscode
31 |
32 | # goland
33 | .idea
34 |
35 | # Test binary, build with `go test -c`
36 | *.test
37 |
38 | # Output of the go coverage tool, specifically when used with LiteIDE
39 | *.out
40 |
41 | .DS_Store
42 | deploy/charts/alertmanager-webhook-adapter-*.tgz
43 |
--------------------------------------------------------------------------------
/pkg/models/templates/README.md:
--------------------------------------------------------------------------------
1 | # Templates
2 |
3 | ## Reference
4 |
5 | ### feishu
6 |
7 | - https://open.feishu.cn/document/common-capabilities/message-card/message-cards-content/using-markdown-tags#abc9b025
8 |
9 | ### weixin (bot) 企业微信群机器人
10 |
11 | - https://developer.work.weixin.qq.com/document/path/91770#markdown%E7%B1%BB%E5%9E%8B
12 |
13 | ### weixinapp 企业微信应用
14 |
15 | - https://developer.work.weixin.qq.com/document/path/90250#markdown%E6%B6%88%E6%81%AF
16 |
17 | ### dingtalk
18 |
19 | - https://open.dingtalk.com/document/orgapp/enterprise-internal-robots-send-markdown-messages
20 |
21 | ### slack
22 |
23 | - https://api.slack.com/reference/surfaces/formatting#basic-formatting
24 |
--------------------------------------------------------------------------------
/docs/screenshot-zh.md:
--------------------------------------------------------------------------------
1 | ## 内置模板通知截图
2 |
3 | | 企业微信机器人 | 企业微信应用 | 钉钉群机器人 | 飞书群机器人 |
4 | | ---------------------------------------- | ------------------------------------------- | ------------------------------------------ | ---------------------------------------- |
5 | |
|
|
|
|
6 |
7 | | Slack App |
8 | | --------------------------------------- |
9 | |
|
10 |
--------------------------------------------------------------------------------
/deploy/charts/alertmanager-webhook-adapter/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{- define "awa.namespace" -}}
2 | {{- if .Values.namespaceOverride -}}
3 | {{- .Values.namespaceOverride -}}
4 | {{- else -}}
5 | {{- .Release.Namespace -}}
6 | {{- end -}}
7 | {{- end -}}
8 |
9 |
10 | {{- define "awa.fullname" -}}
11 | {{- if .Values.fullnameOverride -}}
12 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
13 | {{- else -}}
14 | {{- $name := default .Chart.Name .Values.nameOverride -}}
15 | {{- if contains $name .Release.Name -}}
16 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}}
17 | {{- else -}}
18 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
19 | {{- end -}}
20 | {{- end -}}
21 | {{- end -}}
22 |
--------------------------------------------------------------------------------
/pkg/senders/feishu.go:
--------------------------------------------------------------------------------
1 | package senders
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bougou/webhook-adapter/channels/feishu"
7 | "github.com/bougou/webhook-adapter/models"
8 | restful "github.com/emicklei/go-restful/v3"
9 | )
10 |
11 | const (
12 | ChannelTypeFeishu = "feishu"
13 | )
14 |
15 | func init() {
16 | RegisterChannelsSenderCreator(ChannelTypeFeishu, createFeishuSender)
17 | }
18 |
19 | func createFeishuSender(request *restful.Request) (models.Sender, error) {
20 | token := request.QueryParameter("token")
21 | if token == "" {
22 | return nil, fmt.Errorf("not token found for feishu channel")
23 | }
24 |
25 | msgType := request.QueryParameter("msg_type")
26 | if msgType == "" {
27 | msgType = "markdown"
28 | }
29 |
30 | var sender models.Sender = feishu.NewSender(token, msgType)
31 | return sender, nil
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/senders/weixin.go:
--------------------------------------------------------------------------------
1 | package senders
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bougou/webhook-adapter/channels/weixin"
7 | "github.com/bougou/webhook-adapter/models"
8 | restful "github.com/emicklei/go-restful/v3"
9 | )
10 |
11 | const (
12 | ChannelTypeWeixin = "weixin"
13 | )
14 |
15 | func init() {
16 | RegisterChannelsSenderCreator(ChannelTypeWeixin, createWeixinSender)
17 | }
18 |
19 | func createWeixinSender(request *restful.Request) (models.Sender, error) {
20 | token := request.QueryParameter("token")
21 | if token == "" {
22 | return nil, fmt.Errorf("not token found for weixin channel")
23 | }
24 |
25 | msgType := request.QueryParameter("msg_type")
26 | if msgType == "" {
27 | msgType = "markdown"
28 | }
29 |
30 | var sender models.Sender = weixin.NewSender(token, msgType)
31 | return sender, nil
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/senders/dingtalk.go:
--------------------------------------------------------------------------------
1 | package senders
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bougou/webhook-adapter/channels/dingtalk"
7 | "github.com/bougou/webhook-adapter/models"
8 | restful "github.com/emicklei/go-restful/v3"
9 | )
10 |
11 | const (
12 | ChannelTypeDingtalk = "dingtalk"
13 | )
14 |
15 | func init() {
16 | RegisterChannelsSenderCreator(ChannelTypeDingtalk, createDingtalkSender)
17 | }
18 |
19 | func createDingtalkSender(request *restful.Request) (models.Sender, error) {
20 | token := request.QueryParameter("token")
21 | if token == "" {
22 | return nil, fmt.Errorf("not token found for dingtalk channel")
23 | }
24 |
25 | msgType := request.QueryParameter("msg_type")
26 | if msgType == "" {
27 | msgType = "markdown"
28 | }
29 |
30 | var sender models.Sender = dingtalk.NewSender(token, msgType)
31 | return sender, nil
32 | }
33 |
--------------------------------------------------------------------------------
/.github/workflows/release-binary.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Release binary
3 |
4 | on:
5 | push:
6 | branches:
7 | - "main"
8 | tags:
9 | - "v*"
10 |
11 | jobs:
12 |
13 | binary-release:
14 | name: binary release
15 | runs-on: ubuntu-latest
16 | if: startsWith(github.event.ref, 'refs/tags/v')
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: set up Go
22 | uses: actions/setup-go@v2
23 | with:
24 | go-version: 1.18
25 |
26 | - name: build & test
27 | run: |
28 | make dependencies
29 | make build-all
30 |
31 | - name: release
32 | uses: marvinpinto/action-automatic-releases@latest
33 | with:
34 | repo_token: "${{ secrets.GITHUB_TOKEN }}"
35 | prerelease: false
36 | files: |
37 | _output/*
38 |
--------------------------------------------------------------------------------
/deploy/k8s/deployment.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: alertmanager-webhook-adapter
6 | namespace: infra
7 | spec:
8 | replicas: 1
9 | selector:
10 | matchLabels:
11 | app: alertmanager-webhook-adapter
12 | template:
13 | metadata:
14 | labels:
15 | app: alertmanager-webhook-adapter
16 | spec:
17 | containers:
18 | - name: webhook
19 | image: bougou/alertmanager-webhook-adapter:v1.1.10
20 | command:
21 | - /alertmanager-webhook-adapter
22 | - --listen-address=:8090
23 | - --signature=MyIDC
24 | - --tmpl-lang=zh
25 | env:
26 | - name: TZ
27 | value: Asia/Shanghai
28 | resources:
29 | requests:
30 | memory: 50Mi
31 | cpu: 100m
32 | limits:
33 | memory: 250Mi
34 | cpu: 500m
35 | restartPolicy: Always
36 |
--------------------------------------------------------------------------------
/pkg/senders/discord.go:
--------------------------------------------------------------------------------
1 | package senders
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bougou/webhook-adapter/channels/discord"
7 | "github.com/bougou/webhook-adapter/models"
8 | restful "github.com/emicklei/go-restful/v3"
9 | )
10 |
11 | const (
12 | ChannelTypeDiscordWebhook = "discord-webhook"
13 | )
14 |
15 | func init() {
16 | RegisterChannelsSenderCreator(ChannelTypeDiscordWebhook, createDiscordWebhookSender)
17 | }
18 |
19 | func createDiscordWebhookSender(request *restful.Request) (models.Sender, error) {
20 | id := request.QueryParameter("id")
21 | if id == "" {
22 | return nil, fmt.Errorf("not id found for discord-webhook channel")
23 | }
24 |
25 | token := request.QueryParameter("token")
26 | if token == "" {
27 | return nil, fmt.Errorf("not token found for discord-webhook channel")
28 | }
29 |
30 | msgType := request.QueryParameter("msg_type")
31 | if msgType == "" {
32 | msgType = "markdown"
33 | }
34 |
35 | var sender models.Sender = discord.NewWebhookSender(id, token)
36 | return sender, nil
37 | }
38 |
--------------------------------------------------------------------------------
/deploy/charts/alertmanager-webhook-adapter/templates/deployment.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: {{ include "awa.fullname" . }}
6 | namespace: {{ include "awa.namespace" . }}
7 | spec:
8 | replicas: 1
9 | selector:
10 | matchLabels:
11 | app: alertmanager-webhook-adapter
12 | template:
13 | metadata:
14 | labels:
15 | app: alertmanager-webhook-adapter
16 | spec:
17 | containers:
18 | - name: webhook
19 | image: {{ .Values.image.name }}:{{ .Values.image.tag }}
20 | command:
21 | - /alertmanager-webhook-adapter
22 | - --listen-address=:8090
23 | - --signature={{ .Values.signature }}
24 | - --tmpl-lang={{ .Values.lang | default "en" }}
25 | env:
26 | - name: TZ
27 | value: {{ .Values.timezone }}
28 | resources:
29 | requests:
30 | memory: 50Mi
31 | cpu: 100m
32 | limits:
33 | memory: 250Mi
34 | cpu: 500m
35 | restartPolicy: Always
36 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/bougou/alertmanager-webhook-adapter
2 |
3 | go 1.20
4 |
5 | replace github.com/bougou/alertmanager-webhook-adapter v0.0.0 => ./
6 |
7 | require (
8 | github.com/bougou/webhook-adapter v0.1.1
9 | github.com/emicklei/go-restful/v3 v3.4.0
10 | github.com/kr/pretty v0.1.0
11 | github.com/spf13/cobra v1.1.3
12 | golang.org/x/text v0.3.3
13 | )
14 |
15 | require (
16 | github.com/bwmarrin/discordgo v0.28.1 // indirect
17 | github.com/gorilla/websocket v1.4.2 // indirect
18 | github.com/inconshreveable/mousetrap v1.0.0 // indirect
19 | github.com/json-iterator/go v1.1.10 // indirect
20 | github.com/kr/text v0.1.0 // indirect
21 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
22 | github.com/modern-go/reflect2 v1.0.1 // indirect
23 | github.com/pkg/errors v0.9.1 // indirect
24 | github.com/slack-go/slack v0.9.4 // indirect
25 | github.com/spf13/pflag v1.0.5 // indirect
26 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
27 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
28 | )
29 |
--------------------------------------------------------------------------------
/pkg/senders/slack.go:
--------------------------------------------------------------------------------
1 | package senders
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/bougou/webhook-adapter/channels/slack"
8 | "github.com/bougou/webhook-adapter/models"
9 | restful "github.com/emicklei/go-restful/v3"
10 | )
11 |
12 | const (
13 | ChannelTypeSlack = "slack"
14 | )
15 |
16 | func init() {
17 | RegisterChannelsSenderCreator(ChannelTypeSlack, createSlackSender)
18 | }
19 |
20 | func createSlackSender(request *restful.Request) (models.Sender, error) {
21 | token := request.QueryParameter("token")
22 | if token == "" {
23 | return nil, fmt.Errorf("not token found for slack")
24 | }
25 | channel := request.QueryParameter("channel")
26 | if channel == "" {
27 | return nil, fmt.Errorf("not channel found for slack")
28 | }
29 | // add # if channel not begin with #
30 | if !strings.HasPrefix(channel, "#") {
31 | channel = "#" + channel
32 | }
33 |
34 | msgType := request.QueryParameter("msg_type")
35 | if msgType == "" {
36 | msgType = "markdown"
37 | }
38 |
39 | var sender models.Sender = slack.NewSender(token, channel, msgType)
40 | return sender, nil
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/senders/weixinapp.go:
--------------------------------------------------------------------------------
1 | package senders
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 |
7 | "github.com/bougou/webhook-adapter/channels/weixinapp"
8 | "github.com/bougou/webhook-adapter/models"
9 | restful "github.com/emicklei/go-restful/v3"
10 | )
11 |
12 | const (
13 | ChannelTypeWeixinApp = "weixinapp"
14 | )
15 |
16 | func init() {
17 | RegisterChannelsSenderCreator(ChannelTypeWeixinApp, createWeixinappSender)
18 | }
19 |
20 | func createWeixinappSender(request *restful.Request) (models.Sender, error) {
21 | corpID := request.QueryParameter("corp_id")
22 | if corpID == "" {
23 | return nil, fmt.Errorf("not core_id found for weixin channel")
24 | }
25 |
26 | agentID := request.QueryParameter("agent_id")
27 | if agentID == "" {
28 | return nil, fmt.Errorf("not agent_id found for weixin channel")
29 | }
30 |
31 | aID, err := strconv.Atoi(agentID)
32 | if err != nil {
33 | return nil, fmt.Errorf("agent_id must be integer")
34 | }
35 |
36 | agentSecret := request.QueryParameter("agent_secret")
37 | if agentSecret == "" {
38 | return nil, fmt.Errorf("not agent_secret found for weixin channel")
39 | }
40 |
41 | toUser := request.QueryParameter("to_user")
42 | toParty := request.QueryParameter("to_party")
43 | toTag := request.QueryParameter("to_tag")
44 |
45 | if toUser == "" && toParty == "" && toTag == "" {
46 | return nil, fmt.Errorf("must specify one of to_user,to_party,to_tag")
47 | }
48 |
49 | msgType := request.QueryParameter("msg_type")
50 | if msgType == "" {
51 | msgType = "markdown"
52 | }
53 |
54 | var sender models.Sender = weixinapp.NewSender(corpID, aID, agentSecret, msgType, toUser, toParty, toTag)
55 | return sender, nil
56 | }
57 |
--------------------------------------------------------------------------------
/.github/workflows/release-docker.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Release docker
3 |
4 | on:
5 | push:
6 | branches:
7 | - "main"
8 | tags:
9 | - "v*"
10 |
11 | jobs:
12 | docker-release:
13 | name: docker release
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: AutoModality/action-clean@v1
18 |
19 | - uses: actions/checkout@v2
20 |
21 | - name: set up Docker Buildx
22 | uses: docker/setup-buildx-action@v1
23 |
24 | - name: docker login
25 | uses: docker/login-action@v1.10.0
26 | with:
27 | # registry: ${{ secrets.DOCKERHUB_ADDR }}
28 | username: ${{ secrets.DOCKERHUB_USER }}
29 | password: ${{ secrets.DOCKERHUB_PASS }}
30 |
31 | - name: prepare short tag
32 | id: prepare_short_tag
33 | run: |
34 | SHORT_TAG=`git describe --abbrev=5 --dirty --tags --always`
35 | echo "::set-output name=image_short_tag::$SHORT_TAG"
36 | echo "::notice title=Build Image Short Tag::$SHORT_TAG"
37 |
38 | - name: prepare tag
39 | id: prepare_tag
40 | run: |
41 | TIME_TAG=`TZ=Zero date +"%y%m%d%H%M%S"`
42 | COMMIT_TAG=`echo $GITHUB_SHA | cut -c 1-7`
43 | TAG="$TIME_TAG-$COMMIT_TAG"
44 | echo "::set-output name=image_tag::$TAG"
45 | echo "::notice title=Build Image Tag::$TAG"
46 |
47 | - name: build and push
48 | uses: docker/build-push-action@v2
49 | with:
50 | push: true
51 | context: .
52 | platforms: linux/amd64,linux/arm64
53 | tags: |
54 | bougou/alertmanager-webhook-adapter:latest
55 | bougou/alertmanager-webhook-adapter:${{ steps.prepare_tag.outputs.image_tag }}
56 | bougou/alertmanager-webhook-adapter:${{ steps.prepare_short_tag.outputs.image_short_tag }}
57 |
--------------------------------------------------------------------------------
/deploy/alertmanager-webhook-adapter.init.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # alertmanager-webhook-adapter
3 | # chkconfig: 345 20 80
4 | # description: alertmanager-webhook-adapter
5 | # processname: alertmanager-webhook-adapter
6 |
7 | listen_address=":8090"
8 | signature="Unknown"
9 |
10 | DAEMON_PATH="/data/alertmanager-webhook-adapter/bin"
11 | DAEMON=alertmanager-webhook-adapter
12 | DAEMONOPTS="--listen-address=$listen_address --signature=$signature"
13 |
14 | NAME=alertmanager-webhook-adapter
15 | DESC="alertmanager-webhook-adapter"
16 | PIDFILE=/var/run/$NAME.pid
17 | SCRIPTNAME=/etc/init.d/$NAME
18 |
19 | case "$1" in
20 | start)
21 | printf "%-50s" "Starting $NAME..."
22 | cd $DAEMON_PATH
23 | PID=`$DAEMON $DAEMONOPTS > /dev/null 2>&1 & echo $!`
24 | #echo "Saving PID" $PID " to " $PIDFILE
25 | if [ -z $PID ]; then
26 | printf "%s\n" "Fail"
27 | else
28 | echo $PID > $PIDFILE
29 | printf "%s\n" "Ok"
30 | fi
31 | ;;
32 | status)
33 | printf "%-50s" "Checking $NAME..."
34 | if [ -f $PIDFILE ]; then
35 | PID=`cat $PIDFILE`
36 | if [ -z "`ps axf | grep ${PID} | grep -v grep`" ]; then
37 | printf "%s\n" "Process dead but pidfile exists"
38 | else
39 | echo "Running"
40 | fi
41 | else
42 | printf "%s\n" "Service not running"
43 | fi
44 | ;;
45 | stop)
46 | printf "%-50s" "Stopping $NAME"
47 | PID=`cat $PIDFILE`
48 | cd $DAEMON_PATH
49 | if [ -f $PIDFILE ]; then
50 | kill -HUP $PID
51 | printf "%s\n" "Ok"
52 | rm -f $PIDFILE
53 | else
54 | printf "%s\n" "pidfile not found"
55 | fi
56 | ;;
57 |
58 | restart)
59 | $0 stop
60 | $0 start
61 | ;;
62 |
63 | *)
64 | echo "Usage: $0 {status|start|stop|restart}"
65 | exit 1
66 | esac
67 |
--------------------------------------------------------------------------------
/cmd/alertmanager-webhook-adapter/app/rootcmd.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/bougou/alertmanager-webhook-adapter/cmd/alertmanager-webhook-adapter/app/options"
9 | "github.com/bougou/alertmanager-webhook-adapter/pkg/version"
10 | "github.com/spf13/cobra"
11 | )
12 |
13 | func NewRootCommand() *cobra.Command {
14 | o := options.NewAppOptions()
15 |
16 | rootCmd := &cobra.Command{
17 | Use: "alertmanager-webhook-adapter",
18 | Short: "alertmanager-webhook-adapter",
19 | Long: `alertmanager-webhook-adapter`,
20 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
21 | if o.Version {
22 | fmt.Printf("Version: %s\n", version.Version)
23 | fmt.Printf("Commit: %s\n", version.Commit)
24 | fmt.Printf("BuildAt: %s\n", version.BuildAt)
25 | return nil
26 | }
27 | return nil
28 | },
29 |
30 | Run: func(cmd *cobra.Command, args []string) {
31 | if o.Version {
32 | return
33 | }
34 |
35 | if err := o.Run(); err != nil {
36 | fmt.Println("Error:", err)
37 | os.Exit(1)
38 | return
39 | }
40 | },
41 | }
42 |
43 | rootCmd.Flags().StringVarP(&o.Addr, "listen-address", "l", "0.0.0.0:8090", "the address to listen")
44 | rootCmd.Flags().StringVarP(&o.Signature, "signature", "s", "未知", "the signature")
45 | rootCmd.Flags().StringVarP(&o.TmplDir, "tmpl-dir", "d", "", "the tmpl dir")
46 | rootCmd.Flags().StringVarP(&o.TmplName, "tmpl-name", "t", "", "the tmpl name")
47 | rootCmd.Flags().StringVarP(&o.TmplDefault, "tmpl-default", "n", "", "the default tmpl name")
48 | rootCmd.Flags().StringVarP(&o.TmplLang, "tmpl-lang", "", "", "the language for template filename")
49 | rootCmd.Flags().BoolVarP(&o.Version, "version", "v", false, "show version")
50 | rootCmd.Flags().BoolVarP(&o.Debug, "debug", "", false, "enable verbose output ")
51 |
52 | rootCmd.Flags().AddGoFlagSet(flag.CommandLine)
53 |
54 | return rootCmd
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/models/templates/templates.go:
--------------------------------------------------------------------------------
1 | package templates
2 |
3 | import (
4 | _ "embed"
5 | )
6 |
7 | //go:embed default.tmpl
8 | var DefaultTmpl string
9 |
10 | //go:embed default.zh.tmpl
11 | var DefaultTmplZH string
12 |
13 | //go:embed weixin.tmpl
14 | var DefaultTmplWeixin string
15 |
16 | //go:embed weixin.zh.tmpl
17 | var DefaultTmplWeixinZH string
18 |
19 | //go:embed weixinapp.tmpl
20 | var DefaultTmplWeixinapp string
21 |
22 | //go:embed weixinapp.zh.tmpl
23 | var DefaultTmplWeixinappZH string
24 |
25 | //go:embed dingtalk.tmpl
26 | var DefaultTmplDingTalk string
27 |
28 | //go:embed dingtalk.zh.tmpl
29 | var DefaultTmplDingTalkZH string
30 |
31 | //go:embed feishu.tmpl
32 | var DefaultTmplFeishu string
33 |
34 | //go:embed feishu.zh.tmpl
35 | var DefaultTmplFeishuZH string
36 |
37 | //go:embed slack.tmpl
38 | var DefaultTmplSlack string
39 |
40 | //go:embed slack.zh.tmpl
41 | var DefaultTmplSlackZH string
42 |
43 | //go:embed discord-webhook.tmpl
44 | var DefaultTmplDiscordWebhook string
45 |
46 | var DefaultTmplByLang = map[string]string{
47 | "en": DefaultTmpl,
48 | "zh": DefaultTmplZH,
49 | }
50 |
51 | // Must define for every supported channel
52 | var ChannelsDefaultTmplMapByLang = map[string]map[string]string{
53 | "en": {
54 | "dingtalk": DefaultTmplDingTalk,
55 | "feishu": DefaultTmplFeishu,
56 | "slack": DefaultTmplSlack,
57 | "weixin": DefaultTmplWeixin,
58 | "weixinapp": DefaultTmplWeixinapp,
59 | "discord-webhook": DefaultTmplDiscordWebhook,
60 | },
61 | "zh": {
62 | "dingtalk": DefaultTmplDingTalkZH,
63 | "feishu": DefaultTmplFeishuZH,
64 | "slack": DefaultTmplSlackZH,
65 | "weixin": DefaultTmplWeixinZH,
66 | "weixinapp": DefaultTmplWeixinappZH,
67 | },
68 | }
69 |
70 | func DefaultSupportedLangs() []string {
71 | res := make([]string, 0)
72 | for k := range DefaultTmplByLang {
73 | res = append(res, k)
74 | }
75 | return res
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/models/kv.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "sort"
4 |
5 | // Pair is a key/value string pair.
6 | type Pair struct {
7 | Name, Value string
8 | }
9 |
10 | // Pairs is a list of key/value string pairs.
11 | type Pairs []Pair
12 |
13 | // Names returns a list of names of the pairs.
14 | func (ps Pairs) Names() []string {
15 | ns := make([]string, 0, len(ps))
16 | for _, p := range ps {
17 | ns = append(ns, p.Name)
18 | }
19 | return ns
20 | }
21 |
22 | // Values returns a list of values of the pairs.
23 | func (ps Pairs) Values() []string {
24 | vs := make([]string, 0, len(ps))
25 | for _, p := range ps {
26 | vs = append(vs, p.Value)
27 | }
28 | return vs
29 | }
30 |
31 | // KV is a set of key/value string pairs.
32 | type KV map[string]string
33 |
34 | // SortedPairs returns a sorted list of key/value pairs.
35 | func (kv KV) SortedPairs() Pairs {
36 | var (
37 | pairs = make([]Pair, 0, len(kv))
38 | keys = make([]string, 0, len(kv))
39 | sortStart = 0
40 | )
41 | for k := range kv {
42 | if k == "alertname" {
43 | keys = append([]string{k}, keys...)
44 | sortStart = 1
45 | } else {
46 | keys = append(keys, k)
47 | }
48 | }
49 | sort.Strings(keys[sortStart:])
50 |
51 | for _, k := range keys {
52 | pairs = append(pairs, Pair{k, kv[k]})
53 | }
54 | return pairs
55 | }
56 |
57 | // Remove returns a copy of the key/value set without the given keys.
58 | func (kv KV) Remove(keys []string) KV {
59 | keySet := make(map[string]struct{}, len(keys))
60 | for _, k := range keys {
61 | keySet[k] = struct{}{}
62 | }
63 |
64 | res := KV{}
65 | for k, v := range kv {
66 | if _, ok := keySet[k]; !ok {
67 | res[k] = v
68 | }
69 | }
70 | return res
71 | }
72 |
73 | // Names returns the names of the label names in the LabelSet.
74 | func (kv KV) Names() []string {
75 | return kv.SortedPairs().Names()
76 | }
77 |
78 | // Values returns a list of the values in the LabelSet.
79 | func (kv KV) Values() []string {
80 | return kv.SortedPairs().Values()
81 | }
82 |
--------------------------------------------------------------------------------
/tests/grafana-alert.json:
--------------------------------------------------------------------------------
1 | {
2 | "receiver": "test-webhook",
3 | "status": "firing",
4 | "alerts": [
5 | {
6 | "status": "firing",
7 | "labels": {
8 | "alertname": "CPU Usage High",
9 | "grafana_folder": "alert",
10 | "testkey": "testvalue"
11 | },
12 | "annotations": {},
13 | "startsAt": "2023-03-29T09:41:10Z",
14 | "endsAt": "0001-01-01T00:00:00Z",
15 | "generatorURL": "http://localhost/grafana/alerting/grafana/fOO_kffVz/view?orgId=1",
16 | "fingerprint": "68d4e65db3ad9208",
17 | "silenceURL": "http://localhost/grafana/alerting/silence/new?alertmanager=grafana\u0026matcher=alertname%3DCPU+Usage+High\u0026matcher=grafana_folder%3Dalert\u0026matcher=testkey%3Dtestvalue",
18 | "dashboardURL": "http://localhost/grafana/d/200ac8fdbfbb74b39aff88118e4d1c2c?orgId=1",
19 | "panelURL": "http://localhost/grafana/d/200ac8fdbfbb74b39aff88118e4d1c2c?orgId=1\u0026viewPanel=1",
20 | "values": {
21 | "C": 12,
22 | "D": 1
23 | },
24 | "valueString": "[ var='D' labels={} value=1 ], [ var='C' labels={} value=12 ]"
25 | }
26 | ],
27 | "groupLabels": {
28 | "alertname": "CPU Usage High",
29 | "grafana_folder": "alert"
30 | },
31 | "commonLabels": {
32 | "alertname": "CPU Usage High",
33 | "grafana_folder": "alert",
34 | "testkey": "testvalue"
35 | },
36 | "commonAnnotations": {},
37 | "externalURL": "http://localhost/grafana/",
38 | "version": "1",
39 | "groupKey": "{}/{}:{alertname=\"CPU Usage High\", grafana_folder=\"alert\"}",
40 | "truncatedAlerts": 0,
41 | "orgId": 1,
42 | "title": "[FIRING:1] CPU Usage High alert (testvalue)",
43 | "state": "alerting",
44 | "message": "**Firing**\n\nValue: C=12, D=1\nLabels:\n - alertname = CPU Usage High\n - grafana_folder = alert\n - testkey = testvalue\nAnnotations:\nSource: http://localhost/grafana/alerting/grafana/fOO_kffVz/view?orgId=1\nSilence: http://localhost/grafana/alerting/silence/new?alertmanager=grafana\u0026matcher=alertname%3DCPU+Usage+High\u0026matcher=grafana_folder%3Dalert\u0026matcher=testkey%3Dtestvalue\nDashboard: http://localhost/grafana/d/200ac8fdbfbb74b39aff88118e4d1c2c?orgId=1\nPanel: http://localhost/grafana/d/200ac8fdbfbb74b39aff88118e4d1c2c?orgId=1\u0026viewPanel=1\n"
45 | }
46 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | APP_VERSION ?= $(shell git describe --abbrev=5 --dirty --tags --always)
2 | GIT_COMMIT := $(shell git rev-parse --short=8 HEAD)
3 | BUILD_TIME := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
4 |
5 | BINDIR := $(PWD)/bin
6 | OUTPUT_DIR := $(PWD)/_output
7 |
8 | GOOS ?= $(shell uname -s | tr '[:upper:]' '[:lower:]')
9 | GOARCH ?= amd64
10 |
11 | LDFLAGS := $(LDFLAGS) -X github.com/bougou/alertmanager-webhook-adapter/pkg/version.Version=$(APP_VERSION)
12 | LDFLAGS := $(LDFLAGS) -X github.com/bougou/alertmanager-webhook-adapter/pkg/version.Commit=$(GIT_COMMIT)
13 | LDFLAGS := $(LDFLAGS) -X github.com/bougou/alertmanager-webhook-adapter/pkg/version.BuildAt=$(BUILD_TIME)
14 |
15 | PATH := $(BINDIR):$(PATH)
16 | SHELL := env PATH='$(PATH)' /bin/sh
17 |
18 | all: build
19 |
20 | # Run tests
21 | test: fmt vet
22 | @# Disable --race until https://github.com/kubernetes-sigs/controller-runtime/issues/1171 is fixed.
23 | ginkgo --randomizeAllSpecs --randomizeSuites --failOnPending --flakeAttempts=2 \
24 | --cover --coverprofile cover.out --trace --progress $(TEST_ARGS)\
25 | ./pkg/... ./cmd/...
26 |
27 | # Build alertmanager-webhook-adapter binary
28 | build: fmt vet
29 | go build -ldflags "$(LDFLAGS)" -o $(OUTPUT_DIR)/alertmanager-webhook-adapter ./cmd/alertmanager-webhook-adapter
30 |
31 | # Cross compiler
32 | build-all: fmt vet
33 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -a -o $(OUTPUT_DIR)/alertmanager-webhook-adapter-$(APP_VERSION)-linux-amd64 ./cmd/alertmanager-webhook-adapter
34 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -a -o $(OUTPUT_DIR)/alertmanager-webhook-adapter-$(APP_VERSION)-linux-arm64 ./cmd/alertmanager-webhook-adapter
35 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -a -o $(OUTPUT_DIR)/alertmanager-webhook-adapter-$(APP_VERSION)-darwin-amd64 ./cmd/alertmanager-webhook-adapter
36 | CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -a -o $(OUTPUT_DIR)/alertmanager-webhook-adapter-$(APP_VERSION)-darwin-arm64 ./cmd/alertmanager-webhook-adapter
37 |
38 | # Run go fmt against code
39 | fmt:
40 | go fmt ./pkg/... ./cmd/...
41 |
42 | # Run go vet against code
43 | vet:
44 | go vet ./pkg/... ./cmd/...
45 |
46 | lint:
47 | $(BINDIR)/golangci-lint run --timeout 2m0s ./pkg/... ./cmd/...
48 |
49 | dependencies:
50 | test -d $(BINDIR) || mkdir $(BINDIR)
51 | GOBIN=$(BINDIR) go install github.com/onsi/ginkgo/ginkgo@v1.16.4
52 |
53 | curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $(BINDIR) latest
54 |
--------------------------------------------------------------------------------
/cmd/alertmanager-webhook-adapter/app/options/options.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "os"
9 | "path/filepath"
10 | "strings"
11 |
12 | restful "github.com/emicklei/go-restful/v3"
13 |
14 | "github.com/bougou/alertmanager-webhook-adapter/pkg/api"
15 | "github.com/bougou/alertmanager-webhook-adapter/pkg/models"
16 | "github.com/bougou/alertmanager-webhook-adapter/pkg/models/templates"
17 | )
18 |
19 | type AppOptions struct {
20 | Addr string
21 | Signature string
22 | TmplDir string
23 | TmplName string
24 | TmplDefault string
25 | TmplLang string
26 | Version bool
27 | Debug bool
28 | }
29 |
30 | func NewAppOptions() *AppOptions {
31 | return &AppOptions{}
32 | }
33 |
34 | func (o *AppOptions) Run() error {
35 | execFile, err := os.Executable()
36 | if err != nil {
37 | panic("fatal")
38 | }
39 |
40 | // If using builtin templates (o.TmplDir == ""), then we must check whether or not the specified lang is supported.
41 | if o.TmplLang != "" && o.TmplDir == "" {
42 | if _, exists := templates.DefaultTmplByLang[o.TmplLang]; !exists {
43 | return fmt.Errorf("the builtin templates does not support specified lang (%s), builtin supported langs: (%s)", o.TmplLang, strings.Join(templates.DefaultSupportedLangs(), ","))
44 | }
45 | if err := models.LoadDefaultTemplate(o.TmplLang); err != nil {
46 | return fmt.Errorf("load default template for lang (%s) failed, err: %s", o.TmplLang, err)
47 | }
48 | }
49 |
50 | if o.TmplDir == "" && (o.TmplName != "" || o.TmplDefault != "") {
51 | fmt.Println("Warning, there is no meaning to specify --tmpl-name or --tmpl-default option without specify --tmpl-dir option, just ignored.")
52 | }
53 |
54 | if o.TmplDir != "" {
55 | if o.TmplName != "" && o.TmplDefault != "" {
56 | fmt.Println("Warning, there is no meaning to specify --tmpl-name and --tmpl-default options together, --tmpl-default is ignored.")
57 | o.TmplDefault = ""
58 | }
59 |
60 | if !filepath.IsAbs(o.TmplDir) {
61 | o.TmplDir = filepath.Join(filepath.Dir(execFile), o.TmplDir)
62 | }
63 |
64 | if err := models.LoadTemplate(o.TmplDir, o.TmplName, o.TmplDefault, o.TmplLang); err != nil {
65 | msg := fmt.Sprintf("Load templates from dir (%s) failed, err: %s", o.TmplDir, err)
66 | return errors.New(msg)
67 | }
68 | }
69 |
70 | fmt.Println("Signature: ", o.Signature)
71 | if o.Signature == "未知" {
72 | fmt.Println("Warn, you are using the default signature, we suggest to specify a custom signature by --signature option.")
73 | }
74 |
75 | httpProxy := os.Getenv("HTTP_PROXY")
76 | httpsProxy := os.Getenv("HTTPS_PROXY")
77 | noProxy := os.Getenv("NO_PROXY")
78 | if httpProxy != "" || httpsProxy != "" {
79 | fmt.Println("Found http proxy from environment variables:")
80 | fmt.Printf(" HTTP_PROXY: (%s)\n", httpProxy)
81 | fmt.Printf(" HTTPS_PROXY: (%s)\n", httpsProxy)
82 | fmt.Printf(" NO_PROXY: (%s)\n", noProxy)
83 | }
84 |
85 | container := restful.DefaultContainer
86 |
87 | controller := api.NewController(o.Signature)
88 | controller.WithDebug(o.Debug)
89 |
90 | controller.Install(container)
91 |
92 | s := &http.Server{
93 | Addr: o.Addr,
94 | Handler: container,
95 | }
96 | log.Printf("start listening at %s", s.Addr)
97 | return s.ListenAndServe()
98 | }
99 |
--------------------------------------------------------------------------------
/tests/alert-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | dingtalk() {
4 | token="${DINGTALK_TOKEN}"
5 | channel_type="dingtalk"
6 | msg_type="markdown"
7 | payload=$(cat ./alert.json)
8 |
9 | echo "$payload" | curl -s -H "Content-Type: application/json" -v -XPOST "http://127.0.0.1:8090/webhook/send?channel_type=${channel_type}&token=${token}&msg_type=${msg_type}" -d @-
10 | }
11 |
12 | feishu() {
13 | token="${FEISHU_TOKEN}"
14 | channel_type="feishu"
15 | msg_type="markdown"
16 |
17 | payload=$(cat ./alert.json)
18 |
19 | echo "$payload" | curl -s -H "Content-Type: application/json" -v -XPOST "http://127.0.0.1:8090/webhook/send?channel_type=${channel_type}&token=${token}&msg_type=${msg_type}" -d @-
20 |
21 | # curl -X POST -H "Content-Type: application/json" -d '{"msg_type":"interactive","card":{"elements":[{"tag":"div","text":{"tag":"lark_md","content":"Hello"}}]}}'
22 | }
23 |
24 | slack() {
25 | token="${SLACK_APP_TOKEN}"
26 | channel_type="slack"
27 | msg_type="markdown"
28 | channel="jenkins-ci"
29 |
30 | payload=$(cat ./alert.json)
31 |
32 | echo "$payload" | curl -s -H "Content-Type: application/json" -v -XPOST "http://127.0.0.1:8090/webhook/send?channel_type=${channel_type}&token=${token}&channel=${channel}" -d @-
33 |
34 | ## Invite the slack app to the channel, then the slack app can send messages to this channel.
35 | # /invite @BOT_NAME
36 | }
37 |
38 | weixin() {
39 | token="${WEIXIN_TOKEN}"
40 | channel_type="weixin"
41 | msg_type="markdown"
42 |
43 | payload=$(cat ./alert.json)
44 |
45 | echo "$payload" | curl -s -H "Content-Type: application/json" -v -XPOST "http://127.0.0.1:8090/webhook/send?channel_type=${channel_type}&token=${token}&msg_type=${msg_type}" -d @-
46 | }
47 |
48 | weixinapp() {
49 | corpID="${WEIXIN_APP_CORP_ID}"
50 | agentID=${WEIXIN_APP_AGENT_ID}
51 | agentSecret="${WEIXIN_APP_SECRET}"
52 |
53 | toParty=2
54 |
55 | channel_type="weixinapp"
56 | msg_type="markdown"
57 |
58 | payload=$(cat ./alert.json)
59 |
60 | echo "$payload" | curl -s -H "Content-Type: application/json" -v -XPOST "http://127.0.0.1:8090/webhook/send?channel_type=${channel_type}&msg_type=${msg_type}&corp_id=${corpID}&agent_id=${agentID}&agent_secret=${agentSecret}&to_party=${toParty}" -d @-
61 | }
62 |
63 | discord-webhook() {
64 | id="${DISCORD_WEBHOOK_ID}"
65 | token="${DISCORD_WEBHOOK_TOKEN}"
66 |
67 | channel_type="discord-webhook"
68 | msg_type="markdown"
69 | payload=$(cat ./alert.json)
70 |
71 | echo "$payload" | curl -s -H "Content-Type: application/json" -v -XPOST "http://127.0.0.1:8090/webhook/send?channel_type=${channel_type}&msg_type=${msg_type}&id=${id}&token=${token}" -d @-
72 | }
73 |
74 |
75 | failed-test-1() {
76 | corpID="${WEIXIN_APP_CORP_ID}"
77 | agentID=${WEIXIN_APP_AGENT_ID}
78 | agentSecret="${WEIXIN_APP_SECRET}"
79 |
80 | toParty=2
81 |
82 | channel_type="notsupported"
83 | msg_type="markdown"
84 |
85 | payload=$(cat ./alert.json)
86 |
87 | echo "$payload" | curl -s -H "Content-Type: application/json" -v -XPOST "http://127.0.0.1:8090/webhook/send?channel_type=${channel_type}&msg_type=${msg_type}&corp_id=${corpID}&agent_id=${agentID}&agent_secret=${agentSecret}&to_party=${toParty}" -d @-
88 | }
89 |
90 | weixin_fail_msg_type() {
91 | token="${WEIXIN_TOKEN}"
92 | channel_type="weixin"
93 | msg_type="type-not-exist"
94 |
95 | payload=$(cat ./alert.json)
96 |
97 | echo "$payload" | curl -s -H "Content-Type: application/json" -v -XPOST "http://127.0.0.1:8090/webhook/send?channel_type=${channel_type}&token=${token}&msg_type=${msg_type}" -d @-
98 | }
99 |
--------------------------------------------------------------------------------
/pkg/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "net/http"
8 |
9 | promModels "github.com/bougou/alertmanager-webhook-adapter/pkg/models"
10 | "github.com/bougou/alertmanager-webhook-adapter/pkg/senders"
11 | restful "github.com/emicklei/go-restful/v3"
12 | "github.com/kr/pretty"
13 | )
14 |
15 | type Controller struct {
16 | signature string
17 | debug bool
18 | }
19 |
20 | func NewController(signature string) *Controller {
21 | return &Controller{
22 | signature: signature,
23 | }
24 | }
25 |
26 | func (c *Controller) WithDebug(debug bool) *Controller {
27 | if debug {
28 | fmt.Println("debug mode enabled")
29 | }
30 | c.debug = debug
31 | return c
32 | }
33 |
34 | func (c *Controller) Install(container *restful.Container) {
35 |
36 | ws := new(restful.WebService)
37 | ws.Path("/webhook/send")
38 |
39 | ws.Route(
40 | ws.POST("/").To(c.send),
41 | )
42 |
43 | container.Add(ws)
44 | }
45 |
46 | func (c *Controller) logf(format string, a ...any) error {
47 | if c.debug {
48 | _, err := fmt.Printf(format, a...)
49 | return err
50 | }
51 |
52 | return nil
53 | }
54 |
55 | func (c *Controller) log(a ...any) error {
56 | if c.debug {
57 | _, err := fmt.Println(a...)
58 | return err
59 | }
60 |
61 | return nil
62 | }
63 |
64 | func (c *Controller) send(request *restful.Request, response *restful.Response) {
65 | c.logf("Got request : %s\n", request.Request.URL.String())
66 |
67 | raw, err := io.ReadAll(request.Request.Body)
68 | if err != nil {
69 | errmsg := fmt.Sprintf("Err: read request body failed, err: %s", err)
70 | c.log(errmsg)
71 | response.WriteHeaderAndJson(http.StatusBadRequest, errmsg, restful.MIME_JSON)
72 | return
73 | }
74 |
75 | promMsg := &promModels.AlertmanagerWebhookMessage{}
76 | if err := json.Unmarshal(raw, promMsg); err != nil {
77 | errmsg := fmt.Sprintf("Err: unmarshal body failed, err: %s", err)
78 | c.log(errmsg)
79 | response.WriteHeaderAndJson(http.StatusBadRequest, errmsg, restful.MIME_JSON)
80 | return
81 | }
82 | promMsg.SetMessageAt().SetSignature(c.signature)
83 |
84 | channelType := request.QueryParameter("channel_type")
85 | if channelType == "" {
86 | errmsg := "Err: no channel_type found"
87 | c.log(errmsg)
88 | response.WriteHeaderAndJson(http.StatusBadRequest, errmsg, restful.MIME_JSON)
89 | return
90 | }
91 |
92 | senderCreator, exists := senders.ChannelsSenderCreatorMap[channelType]
93 | if !exists {
94 | errmsg := fmt.Sprintf("Err: not supported channel_type of (%s)", channelType)
95 | c.log(errmsg)
96 | response.WriteHeaderAndJson(http.StatusBadRequest, errmsg, restful.MIME_JSON)
97 | return
98 | }
99 |
100 | sender, err := senderCreator(request)
101 | if err != nil {
102 | errmsg := fmt.Sprintf("Err: create sender failed, %v", err)
103 | c.log(errmsg)
104 | response.WriteHeaderAndJson(http.StatusBadRequest, errmsg, restful.MIME_JSON)
105 | return
106 | }
107 |
108 | payload, err := promMsg.ToPayload(channelType, raw)
109 | if err != nil {
110 | errmsg := fmt.Sprintf("Err: create msg payload failed, %v", err)
111 | c.log(errmsg)
112 | response.WriteHeaderAndJson(http.StatusInternalServerError, errmsg, restful.MIME_JSON)
113 | return
114 | }
115 | if c.debug {
116 | pretty.Println(payload)
117 |
118 | fmt.Println(">>> Payload Markdown")
119 | fmt.Print(payload.Markdown)
120 | }
121 |
122 | if err := sender.Send(payload); err != nil {
123 | errmsg := fmt.Sprintf("Err: sender send failed, %v", err)
124 | c.log(errmsg)
125 | response.WriteHeaderAndJson(http.StatusInternalServerError, errmsg, restful.MIME_JSON)
126 | return
127 | }
128 |
129 | c.logf("Send succeed: %s\n", request.Request.URL.String())
130 | response.WriteHeader(http.StatusNoContent)
131 | }
132 |
--------------------------------------------------------------------------------
/pkg/models/templates/default.zh.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 |
4 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
7 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
8 |
9 | {{- ` • ` }}
10 |
11 | {{- if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
12 |
13 | {{- ` • ` }}
14 |
15 | {{- if gt (.Alerts.Firing|len) 0 }}告警中:{{ .Alerts.Firing|len }}{{ end }}
16 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
17 | {{- if gt (.Alerts.Resolved|len) 0 }}已恢复:{{ .Alerts.Resolved|len }}{{ end }}
18 |
19 | {{ end }}
20 |
21 |
22 | {{ define "__externalURL" -}}
23 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
24 | {{- end }}
25 |
26 | {{ define "__alertinstance" -}}
27 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
28 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
29 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
30 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
31 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
32 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
33 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
34 | {{- end -}}
35 | {{- end }}
36 |
37 | {{ define "__alert_list" }}
38 | {{ range . }}
39 | > 告警名称: {{ if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
40 | >
41 | > Severity:{{ ` ` }}
42 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
43 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
44 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
45 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
46 | >
47 | > 告警实例: `{{ template "__alertinstance" . }}`
48 | >
49 | {{- if .Labels.region }}
50 | > 地域: {{ .Labels.region }}
51 | >
52 | {{- end }}
53 | {{- if .Labels.zone }}
54 | > 可用区: {{ .Labels.zone }}
55 | >
56 | {{- end }}
57 | {{- if .Labels.product }}
58 | > 产品: {{ .Labels.product }}
59 | >
60 | {{- end }}
61 | {{- if .Labels.component }}
62 | > 组件: {{ .Labels.component }}
63 | >
64 | {{- end }}
65 | > 告警状态: {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }}{{ .Status | toUpper }}
66 | >
67 | > 开始时间: {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
68 | >
69 | > 结束时间: {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
70 | {{- if eq .Status "firing" }}
71 | >
72 | > 告警描述: {{ .Annotations.description }}
73 | {{- end }}
74 |
75 | {{ end }}
76 | {{ end }}
77 |
78 | {{ define "__alert_summary" -}}
79 | {{ range . }}
80 | {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }}{{ template "__alertinstance" . }}
81 | {{ end }}
82 | {{ end }}
83 |
84 | {{ define "prom.title" -}}
85 | {{ template "__subject" . }}
86 | {{ end }}
87 |
88 |
89 | {{ define "prom.markdown" }}
90 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
91 | #### 摘要
92 |
93 | {{ if gt (.Alerts.Firing|len ) 0 }}
94 | ##### 🚨 Firing {{ .Alerts.Firing|len }} alerts
95 | {{ template "__alert_summary" .Alerts.Firing }}
96 | {{ end }}
97 |
98 |
99 | {{ if gt (.Alerts.Resolved|len) 0 }}
100 | ##### ✅ Resolved {{ .Alerts.Resolved|len }} alerts
101 | {{ template "__alert_summary" .Alerts.Resolved }}
102 | {{ end }}
103 |
104 | #### 详情
105 |
106 | {{ if gt (.Alerts.Firing|len ) 0 }}
107 | ##### 🚨 Firing {{ .Alerts.Firing|len }} alerts
108 | {{ template "__alert_list" .Alerts.Firing }}
109 | {{ end }}
110 |
111 | {{ if gt (.Alerts.Resolved|len) 0 }}
112 | ##### ✅ Resolved {{ .Alerts.Resolved|len }} alerts
113 | {{ template "__alert_list" .Alerts.Resolved }}
114 | {{ end }}
115 |
116 | {{ end }}
117 |
118 | {{ define "prom.text" }}
119 | {{ template "prom.markdown" . }}
120 | {{ end }}
121 |
--------------------------------------------------------------------------------
/pkg/models/templates/slack.zh.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 |
4 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
7 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
8 |
9 | {{- ` • ` }}
10 |
11 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
12 |
13 | {{- ` • ` }}
14 |
15 | {{- if gt (.Alerts.Firing|len) 0 }}告警中:{{ .Alerts.Firing|len }}{{ end }}
16 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
17 | {{- if gt (.Alerts.Resolved|len) 0 }}已恢复:{{ .Alerts.Resolved|len }}{{ end }}
18 |
19 | {{ end }}
20 |
21 |
22 | {{ define "__externalURL" -}}
23 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
24 | {{- end }}
25 |
26 | {{ define "__alertinstance" -}}
27 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
28 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
29 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
30 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
31 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
32 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
33 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
34 | {{- end -}}
35 | {{- end }}
36 |
37 | {{ define "__alert_list" }}
38 | {{ range . }}
39 | > 告警名称: {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
40 | > 告警级别:{{ ` ` }}
41 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
42 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
43 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
44 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
45 | > 实例: `{{ template "__alertinstance" . }}`
46 | {{- if .Labels.region }}
47 | > 地域: {{ .Labels.region }}
48 | {{- end }}
49 | {{- if .Labels.zone }}
50 | > 可用区: {{ .Labels.zone }}
51 | {{- end }}
52 | {{- if .Labels.product }}
53 | > 产品: {{ .Labels.product }}
54 | {{- end }}
55 | {{- if .Labels.component }}
56 | > 组件: {{ .Labels.component }}
57 | {{- end }}
58 | > 告警状态: {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }} {{ .Status | toUpper }}
59 | > 开始时间: {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
60 | > 结束时间: {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
61 | {{- if eq .Status "firing" }}
62 | > 告警描述: {{ if .Annotations.description_cn }}{{ .Annotations.description_cn }}{{ else }}{{ .Annotations.description }}{{ end }}
63 | {{- end }}
64 | {{ end }}
65 | {{- end }}
66 |
67 |
68 | {{ define "__alert_summary" -}}
69 | {{ range . -}}
70 | • {{ template "__alertinstance" . }}
71 | {{ end }}
72 | {{ end }}
73 |
74 | {{ define "prom.title" -}}
75 | {{ template "__subject" . }}
76 | {{ end }}
77 |
78 | {{ define "prom.markdown" }}
79 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
80 |
81 | *摘要*
82 |
83 | {{ if gt (.Alerts.Firing|len ) 0 -}}
84 | 🚨 触发中告警 [{{ .Alerts.Firing|len }}]
85 |
86 | {{ template "__alert_summary" .Alerts.Firing }}
87 | {{- end -}}
88 |
89 | {{ if gt (.Alerts.Resolved|len) 0 -}}
90 | ✅ 已恢复告警 [{{ .Alerts.Resolved|len }}]
91 |
92 | {{ template "__alert_summary" .Alerts.Resolved }}
93 | {{- end -}}
94 |
95 | *详请*
96 |
97 | {{ if gt (.Alerts.Firing|len ) 0 -}}
98 | 🚨 触发中告警 [{{ .Alerts.Firing|len }}]
99 |
100 | {{- template "__alert_list" .Alerts.Firing }}
101 | {{- end -}}
102 |
103 | {{ if gt (.Alerts.Resolved|len) 0 }}
104 | ✅ 已恢复告警 [{{ .Alerts.Resolved|len }}]
105 |
106 | {{- template "__alert_list" .Alerts.Resolved }}
107 | {{- end -}}
108 | {{ end }}
109 |
110 | {{ define "prom.text" }}
111 | {{ template "prom.markdown" . }}
112 | {{ end }}
113 |
--------------------------------------------------------------------------------
/pkg/models/templates/default.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 |
4 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
7 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
8 |
9 | {{- ` • ` }}
10 |
11 | {{- if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
12 |
13 | {{- ` • ` }}
14 |
15 | {{- if gt (.Alerts.Firing|len) 0 }}Firing:{{ .Alerts.Firing|len }}{{ end }}
16 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
17 | {{- if gt (.Alerts.Resolved|len) 0 }}Resolved:{{ .Alerts.Resolved|len }}{{ end }}
18 |
19 | {{ end }}
20 |
21 |
22 | {{ define "__externalURL" -}}
23 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
24 | {{- end }}
25 |
26 | {{ define "__alertinstance" -}}
27 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
28 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
29 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
30 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
31 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
32 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
33 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
34 | {{- end -}}
35 | {{- end }}
36 |
37 | {{ define "__alert_list" }}
38 | {{ range . }}
39 | > Alert Name: {{ if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
40 | >
41 | > Severity:{{ ` ` }}
42 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
43 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
44 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
45 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
46 | >
47 | > Instance: `{{ template "__alertinstance" . }}`
48 | >
49 | {{- if .Labels.region }}
50 | > Region: {{ .Labels.region }}
51 | >
52 | {{- end }}
53 | {{- if .Labels.zone }}
54 | > Zone: {{ .Labels.zone }}
55 | >
56 | {{- end }}
57 | {{- if .Labels.product }}
58 | > Product: {{ .Labels.product }}
59 | >
60 | {{- end }}
61 | {{- if .Labels.component }}
62 | > Component: {{ .Labels.component }}
63 | >
64 | {{- end }}
65 | > Status: {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }}"> {{ .Status | toUpper }}
66 | >
67 | > Starts At: {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
68 | >
69 | > Ends At: {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
70 | {{- if eq .Status "firing" }}
71 | >
72 | > Description : {{ .Annotations.description }}
73 | {{- end }}
74 |
75 | {{ end }}
76 | {{ end }}
77 |
78 | {{ define "__alert_summary" -}}
79 | {{ range . }}
80 | {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }}"> {{ template "__alertinstance" . }}
81 | {{ end }}
82 | {{ end }}
83 |
84 | {{ define "prom.title" -}}
85 | {{ template "__subject" . }}
86 | {{ end }}
87 |
88 |
89 | {{ define "prom.markdown" }}
90 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
91 | #### Summary
92 |
93 | {{ if gt (.Alerts.Firing|len ) 0 }}
94 | ##### 🚨 Firing {{ .Alerts.Firing|len }} alerts
95 | {{ template "__alert_summary" .Alerts.Firing }}
96 | {{ end }}
97 |
98 |
99 | {{ if gt (.Alerts.Resolved|len) 0 }}
100 | ##### ✅ Resolved {{ .Alerts.Resolved|len }} alerts
101 | {{ template "__alert_summary" .Alerts.Resolved }}
102 | {{ end }}
103 |
104 | #### Detail
105 |
106 | {{ if gt (.Alerts.Firing|len ) 0 }}
107 | ##### 🚨 Firing {{ .Alerts.Firing|len }} alerts
108 | {{ template "__alert_list" .Alerts.Firing }}
109 | {{ end }}
110 |
111 | {{ if gt (.Alerts.Resolved|len) 0 }}
112 | ##### ✅ Resolved {{ .Alerts.Resolved|len }} alerts
113 | {{ template "__alert_list" .Alerts.Resolved }}
114 | {{ end }}
115 |
116 | {{ end }}
117 |
118 | {{ define "prom.text" }}
119 | {{ template "prom.markdown" . }}
120 | {{ end }}
121 |
--------------------------------------------------------------------------------
/pkg/models/templates/discord-webhook.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 |
4 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
7 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
8 |
9 | {{- ` • ` }}
10 |
11 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
12 |
13 | {{- ` • ` }}
14 |
15 | {{- if gt (.Alerts.Firing|len) 0 }}Firing:{{ .Alerts.Firing|len }}{{ end }}
16 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
17 | {{- if gt (.Alerts.Resolved|len) 0 }}Resolved:{{ .Alerts.Resolved|len }}{{ end }}
18 |
19 | {{ end }}
20 |
21 |
22 | {{ define "__externalURL" -}}
23 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
24 | {{- end }}
25 |
26 | {{ define "__alertinstance" -}}
27 | {{ if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
28 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
29 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
30 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
31 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
32 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
33 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
34 | {{- end }}
35 | {{- end }}
36 |
37 | {{ define "__alert_list" }}
38 | {{- range . }}
39 |
40 | > Alert Name: {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
41 | > Alert Level:{{ ` ` }}
42 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
43 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
44 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
45 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
46 | > Instance: `{{ template "__alertinstance" . }}`
47 | {{- if .Labels.region }}
48 | > Region: {{ .Labels.region }}
49 | {{- end }}
50 | {{- if .Labels.zone }}
51 | > Zone: {{ .Labels.zone }}
52 | {{- end }}
53 | {{- if .Labels.product }}
54 | > Product: {{ .Labels.product }}
55 | {{- end }}
56 | {{- if .Labels.component }}
57 | > Component: {{ .Labels.component }}
58 | {{- end }}
59 | > Alert Status: {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }} {{ .Status | toUpper }}
60 | > Start At: {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
61 | > End At: {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
62 | {{- if eq .Status "firing" }}
63 | > Description: {{ if .Annotations.description_en }}{{ .Annotations.description_en }}{{ else }}{{ .Annotations.description }}{{ end }}
64 | {{- end }}
65 | {{- end }}
66 | {{ end }}
67 |
68 |
69 | {{ define "__alert_summary" -}}
70 | {{- range . }}
71 | - {{ template "__alertinstance" . }}
72 | {{- end }}
73 | {{ end }}
74 |
75 | {{ define "prom.title" -}}
76 | {{ template "__subject" . }}
77 | {{ end }}
78 |
79 |
80 | {{ define "prom.markdown" }}
81 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
82 | ## Summary
83 |
84 | {{ if gt (.Alerts.Firing|len ) 0 }}
85 | ### 🚨 Firing [{{ .Alerts.Firing|len }}] alerts
86 | {{ template "__alert_summary" .Alerts.Firing }}
87 | {{ end -}}
88 |
89 | {{ if gt (.Alerts.Resolved|len) 0 -}}
90 | ### ✅ Resolved [{{ .Alerts.Resolved|len }}] alerts
91 | {{ template "__alert_summary" .Alerts.Resolved }}
92 | {{- end }}
93 | ## Detail
94 |
95 | {{ if gt (.Alerts.Firing|len ) 0 -}}
96 | ### 🚨 Firing [{{ .Alerts.Firing|len }}] alerts
97 | {{ template "__alert_list" .Alerts.Firing }}
98 | {{ end -}}
99 |
100 | {{- if gt (.Alerts.Resolved|len) 0 -}}
101 | ### ✅ Resolved [{{ .Alerts.Resolved|len }}] alerts
102 | {{ template "__alert_list" .Alerts.Resolved }}
103 | {{ end }}
104 | {{ end }}
105 |
106 | {{ define "prom.text" }}
107 | {{ template "prom.markdown" . }}
108 | {{ end }}
109 |
--------------------------------------------------------------------------------
/pkg/models/templates/dingtalk.zh.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
4 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
7 | {{- ` • ` }}
8 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
9 | {{- ` • ` }}
10 | {{- if gt (.Alerts.Firing|len) 0 }}告警中:{{ .Alerts.Firing|len }}{{ end }}
11 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
12 | {{- if gt (.Alerts.Resolved|len) 0 }}已恢复:{{ .Alerts.Resolved|len }}{{ end }}
13 | {{ end }}
14 |
15 |
16 | {{ define "__externalURL" -}}
17 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
18 | {{- end }}
19 |
20 | {{ define "__alertinstance" -}}
21 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
22 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
23 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
24 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
25 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
26 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
27 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
28 | {{- end -}}
29 | {{- end }}
30 |
31 | {{ define "__alert_list" }}
32 | {{ range . }}
33 | ---
34 | > **告警名称**: {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
35 | >
36 | > **告警级别**: {{ ` ` }}
37 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
38 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
39 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
40 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
41 | >
42 | > **告警实例**: `{{ template "__alertinstance" . }}`
43 | >
44 | {{- if .Labels.region }}
45 | > **地域**: {{ .Labels.region }}
46 | >
47 | {{- end }}
48 | {{- if .Labels.zone }}
49 | > **可用区**: {{ .Labels.zone }}
50 | >
51 | {{- end }}
52 | {{- if .Labels.product }}
53 | > **产品**: {{ .Labels.product }}
54 | >
55 | {{- end }}
56 | {{- if .Labels.component }}
57 | > **组件**: {{ .Labels.component }}
58 | >
59 | {{- end }}
60 | > **告警状态**: {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }} {{ .Status | toUpper }}
61 | >
62 | > **开始时间**: {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
63 | >
64 | > **结束时间**: {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
65 | >
66 | {{- if eq .Status "firing" }}
67 | > 告警描述: {{ if .Annotations.description_cn }}{{ .Annotations.description_cn }}{{ else }}{{ .Annotations.description }}{{ end }}
68 | >
69 | {{- end }}
70 | {{ end }}
71 | {{ end }}
72 |
73 |
74 | {{ define "__alert_summary" }}
75 | {{ range . }}- {{ template "__alertinstance" . }}
76 | {{ end }}
77 | {{ end }}
78 |
79 | {{ define "prom.title" }}
80 | {{ template "__subject" . }}
81 | {{ end }}
82 |
83 |
84 | {{ define "prom.markdown" }}
85 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
86 | #### **摘要**
87 |
88 | {{ if gt (.Alerts.Firing|len ) 0 }}
89 | ##### **🚨 触发中告警 [{{ .Alerts.Firing|len }}]**
90 | {{ template "__alert_summary" .Alerts.Firing }}
91 | {{ end }}
92 |
93 |
94 | {{ if gt (.Alerts.Resolved|len) 0 }}
95 | ##### **✅ 已恢复告警 [{{ .Alerts.Resolved|len }}]**
96 | {{ template "__alert_summary" .Alerts.Resolved }}
97 | {{ end }}
98 |
99 | #### **详请**
100 |
101 | {{ if gt (.Alerts.Firing|len ) 0 }}
102 | ##### **🚨 触发中告警 [{{ .Alerts.Firing|len }}]**
103 | {{ template "__alert_list" .Alerts.Firing }}
104 | {{ end }}
105 |
106 |
107 | {{ if gt (.Alerts.Resolved|len) 0 }}
108 | ##### **✅ 已恢复告警 [{{ .Alerts.Resolved|len }}]**
109 | {{ template "__alert_list" .Alerts.Resolved }}
110 | {{ end }}
111 | {{ end }}
112 |
113 | {{ define "prom.text" }}
114 | {{ template "prom.markdown" . }}
115 | {{ end }}
116 |
--------------------------------------------------------------------------------
/pkg/models/templates/slack.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 |
4 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
7 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
8 |
9 | {{- ` • ` }}
10 |
11 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
12 |
13 | {{- ` • ` }}
14 |
15 | {{- if gt (.Alerts.Firing|len) 0 }}Firing:{{ .Alerts.Firing|len }}{{ end }}
16 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
17 | {{- if gt (.Alerts.Resolved|len) 0 }}Resolved:{{ .Alerts.Resolved|len }}{{ end }}
18 |
19 | {{ end }}
20 |
21 |
22 | {{ define "__externalURL" -}}
23 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
24 | {{- end }}
25 |
26 | {{ define "__alertinstance" -}}
27 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
28 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
29 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
30 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
31 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
32 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
33 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
34 | {{- end -}}
35 | {{- end }}
36 |
37 | {{ define "__alert_list" }}
38 | {{ range . }}
39 | > Alert Name: {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
40 | > Alert Level:{{ ` ` }}
41 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
42 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
43 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
44 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
45 | > Instance: `{{ template "__alertinstance" . }}`
46 | {{- if .Labels.region }}
47 | > Region: {{ .Labels.region }}
48 | {{- end }}
49 | {{- if .Labels.zone }}
50 | > Zone: {{ .Labels.zone }}
51 | {{- end }}
52 | {{- if .Labels.product }}
53 | > Product: {{ .Labels.product }}
54 | {{- end }}
55 | {{- if .Labels.component }}
56 | > Component: {{ .Labels.component }}
57 | {{- end }}
58 | > Alert Status: {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }} {{ .Status | toUpper }}
59 | > Start At: {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
60 | > End At: {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
61 | {{- if eq .Status "firing" }}
62 | > Description: {{ if .Annotations.description_en }}{{ .Annotations.description_en }}{{ else }}{{ .Annotations.description }}{{ end }}
63 | {{- end }}
64 | {{ end }}
65 | {{- end }}
66 |
67 |
68 | {{ define "__alert_summary" -}}
69 | {{ range . -}}
70 | • {{ template "__alertinstance" . }}
71 | {{ end }}
72 | {{ end }}
73 |
74 | {{ define "prom.title" -}}
75 | {{ template "__subject" . }}
76 | {{ end }}
77 |
78 |
79 | {{ define "prom.markdown" }}
80 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
81 |
82 | *Summary*
83 |
84 | {{ if gt (.Alerts.Firing|len ) 0 -}}
85 | 🚨 Firing [{{ .Alerts.Firing|len }}] alerts
86 |
87 | {{ template "__alert_summary" .Alerts.Firing }}
88 | {{- end -}}
89 |
90 | {{ if gt (.Alerts.Resolved|len) 0 -}}
91 | ✅ Resolved [{{ .Alerts.Resolved|len }}] alerts
92 |
93 | {{ template "__alert_summary" .Alerts.Resolved }}
94 | {{- end -}}
95 |
96 | *Detail*
97 |
98 | {{ if gt (.Alerts.Firing|len ) 0 -}}
99 | 🚨 Firing [{{ .Alerts.Firing|len }}] alerts
100 |
101 | {{- template "__alert_list" .Alerts.Firing }}
102 | {{- end -}}
103 |
104 | {{ if gt (.Alerts.Resolved|len) 0 }}
105 | ✅ Resolved [{{ .Alerts.Resolved|len }}] alerts
106 |
107 | {{- template "__alert_list" .Alerts.Resolved }}
108 | {{- end -}}
109 | {{ end }}
110 |
111 | {{ define "prom.text" }}
112 | {{ template "prom.markdown" . }}
113 | {{ end }}
114 |
--------------------------------------------------------------------------------
/pkg/models/templates/dingtalk.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
4 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
7 | {{- ` • ` }}
8 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
9 | {{- ` • ` }}
10 | {{- if gt (.Alerts.Firing|len) 0 }}Firing:{{ .Alerts.Firing|len }}{{ end }}
11 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
12 | {{- if gt (.Alerts.Resolved|len) 0 }}Resolved:{{ .Alerts.Resolved|len }}{{ end }}
13 | {{ end }}
14 |
15 |
16 | {{ define "__externalURL" -}}
17 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
18 | {{- end }}
19 |
20 | {{ define "__alertinstance" -}}
21 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
22 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
23 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
24 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
25 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
26 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
27 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
28 | {{- end -}}
29 | {{- end }}
30 |
31 | {{ define "__alert_list" }}
32 | {{ range . }}
33 | ---
34 | > **Alert Name**: {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
35 | >
36 | > **Alert Level**: {{ ` ` }}
37 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
38 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
39 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
40 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
41 | >
42 | > **Instance**: `{{ template "__alertinstance" . }}`
43 | >
44 | {{- if .Labels.region }}
45 | > **Region**: {{ .Labels.region }}
46 | >
47 | {{- end }}
48 | {{- if .Labels.zone }}
49 | > **Zone**: {{ .Labels.zone }}
50 | >
51 | {{- end }}
52 | {{- if .Labels.product }}
53 | > **Product**: {{ .Labels.product }}
54 | >
55 | {{- end }}
56 | {{- if .Labels.component }}
57 | > **Component**: {{ .Labels.component }}
58 | >
59 | {{- end }}
60 | > **Alert Status**: {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }} {{ .Status | toUpper }}
61 | >
62 | > **Start At**: {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
63 | >
64 | > **End At**: {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
65 | >
66 | {{- if eq .Status "firing" }}
67 | > **Description**: {{ if .Annotations.description_en }}{{ .Annotations.description_en }}{{ else }}{{ .Annotations.description }}{{ end }}
68 | >
69 | {{- end }}
70 | {{ end }}
71 | {{ end }}
72 |
73 |
74 | {{ define "__alert_summary" }}
75 | {{ range . }}- {{ template "__alertinstance" . }}
76 | {{ end }}
77 | {{ end }}
78 |
79 | {{ define "prom.title" }}
80 | {{ template "__subject" . }}
81 | {{ end }}
82 |
83 |
84 | {{ define "prom.markdown" }}
85 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
86 | #### **Summary**
87 |
88 | {{ if gt (.Alerts.Firing|len ) 0 }}
89 | ##### **🚨 Firing [{{ .Alerts.Firing|len }}] alerts**
90 | {{ template "__alert_summary" .Alerts.Firing }}
91 | {{ end }}
92 |
93 |
94 | {{ if gt (.Alerts.Resolved|len) 0 }}
95 | ##### **✅ Resolved [{{ .Alerts.Resolved|len }}] alerts**
96 | {{ template "__alert_summary" .Alerts.Resolved }}
97 | {{ end }}
98 |
99 | #### **Detail**
100 |
101 | {{ if gt (.Alerts.Firing|len ) 0 }}
102 | ##### **🚨 Firing [{{ .Alerts.Firing|len }}] alerts**
103 | {{ template "__alert_list" .Alerts.Firing }}
104 | {{ end }}
105 |
106 |
107 | {{ if gt (.Alerts.Resolved|len) 0 }}
108 | ##### **✅ Resolved [{{ .Alerts.Resolved|len }}] alerts**
109 | {{ template "__alert_list" .Alerts.Resolved }}
110 | {{ end }}
111 | {{ end }}
112 |
113 | {{ define "prom.text" }}
114 | {{ template "prom.markdown" . }}
115 | {{ end }}
116 |
--------------------------------------------------------------------------------
/tests/alert.json:
--------------------------------------------------------------------------------
1 | {
2 | "receiver": "devops_team",
3 | "status": "firing",
4 | "alerts": [
5 | {
6 | "status": "firing",
7 | "labels": {
8 | "alertgroup": "Linux",
9 | "alertname": "CPUHigh",
10 | "component": "component-x",
11 | "cpu": "cpu-total",
12 | "db": "telegraf",
13 | "description": "cpu usage is high than 20% for 5 minutes",
14 | "host": "10.30.1.160",
15 | "instance": "10.30.1.160",
16 | "product": "product-x",
17 | "region": "region-x",
18 | "severity": "warning",
19 | "vmalert": "10.30.12.22",
20 | "zone": "zone-x"
21 | },
22 | "annotations": {
23 | "description": "cpu usage is high than 20% for 5 minutes"
24 | },
25 | "startsAt": "2021-03-30T20:17:50.814674209+08:00",
26 | "endsAt": "0001-01-01T00:00:00Z",
27 | "generatorURL": "http://10.30.12.22:8429/api/v1/2026138838271548684/17025577897922373885/status",
28 | "fingerprint": "83eb2c81e0da3faf"
29 | },
30 | {
31 | "status": "resolved",
32 | "labels": {
33 | "alertgroup": "Linux",
34 | "alertname": "CPUHigh",
35 | "component": "component-y",
36 | "cpu": "cpu-total",
37 | "db": "telegraf",
38 | "description": "cpu usage is high than 20% for 5 minutes",
39 | "host": "10.30.1.161",
40 | "instance": "10.30.1.161",
41 | "product": "product-y",
42 | "region": "region-y",
43 | "severity": "warning",
44 | "vmalert": "10.30.12.22",
45 | "zone": "zone-y"
46 | },
47 | "annotations": {
48 | "description": "cpu usage is high than 20% for 5 minutes"
49 | },
50 | "startsAt": "2021-03-30T20:17:50.814674209+08:00",
51 | "endsAt": "2021-03-30T21:17:50.814674209+08:00",
52 | "generatorURL": "http://10.30.12.22:8429/api/v1/2026138838271548684/17025577897922373885/status",
53 | "fingerprint": "83eb2c81e0da3faf"
54 | },
55 | {
56 | "status": "firing",
57 | "labels": {
58 | "alertgroup": "Linux",
59 | "alertname": "CPUHigh1",
60 | "component": "component-x",
61 | "cpu": "cpu-total",
62 | "db": "telegraf",
63 | "description": "cpu usage is high than 20% for 5 minutes",
64 | "host": "10.30.1.162",
65 | "instance": "10.30.1.162",
66 | "product": "product-x",
67 | "region": "region-x",
68 | "severity": "critical",
69 | "vmalert": "10.30.12.22",
70 | "zone": "zone-x"
71 | },
72 | "annotations": {
73 | "description": "cpu usage is high than 20% for 5 minutes"
74 | },
75 | "startsAt": "2021-03-30T20:17:50.814674209+08:00",
76 | "endsAt": "0001-01-01T00:00:00Z",
77 | "generatorURL": "http://10.30.12.22:8429/api/v1/2026138838271548684/17025577897922373885/status",
78 | "fingerprint": "83eb2c81e0da3faf"
79 | },
80 | {
81 | "status": "firing",
82 | "labels": {
83 | "alertgroup": "Linux",
84 | "alertname": "CPUHigh2",
85 | "component": "component-x",
86 | "cpu": "cpu-total",
87 | "db": "telegraf",
88 | "description": "cpu usage is high than 20% for 5 minutes",
89 | "host": "10.30.1.163",
90 | "instance": "10.30.1.163",
91 | "product": "product-x",
92 | "region": "region-x",
93 | "severity": "info",
94 | "vmalert": "10.30.12.22",
95 | "zone": "zone-x"
96 | },
97 | "annotations": {
98 | "description": "cpu usage is high than 20% for 5 minutes"
99 | },
100 | "startsAt": "2021-03-30T20:17:50.814674209+08:00",
101 | "endsAt": "0001-01-01T00:00:00Z",
102 | "generatorURL": "http://10.30.12.22:8429/api/v1/2026138838271548684/17025577897922373885/status",
103 | "fingerprint": "83eb2c81e0da3faf"
104 | }
105 | ],
106 | "groupLabels": {
107 | "alertname": "CPUHigh"
108 | },
109 | "commonLabels": {
110 | "alertgroup": "Linux",
111 | "alertname": "CPUHigh",
112 | "component": "component-x",
113 | "cpu": "cpu-total",
114 | "db": "telegraf",
115 | "description": "cpu usage is high than 20% for 5 minutes",
116 | "host": "10.30.1.160",
117 | "product": "product-x",
118 | "region": "region-x",
119 | "severity": "warning",
120 | "vmalert": "10.30.12.22",
121 | "zone": "zone-x"
122 | },
123 | "commonAnnotations": {},
124 | "externalURL": "http://10.30.12.22:9193",
125 | "version": "4",
126 | "groupKey": "{}:{alertname=\"CPUHigh\"}",
127 | "truncatedAlerts": 0
128 | }
129 |
--------------------------------------------------------------------------------
/pkg/models/templates/feishu.zh.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
4 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
7 | {{- ` • ` }}
8 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
9 | {{- ` • ` }}
10 | {{- if gt (.Alerts.Firing|len) 0 }}告警中:{{ .Alerts.Firing|len }}{{ end }}
11 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
12 | {{- if gt (.Alerts.Resolved|len) 0 }}已恢复:{{ .Alerts.Resolved|len }}{{ end }}
13 | {{ end }}
14 |
15 |
16 | {{ define "__externalURL" -}}
17 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
18 | {{- end }}
19 |
20 | {{ define "__alertinstance" -}}
21 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
22 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
23 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
24 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
25 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
26 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
27 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
28 | {{- end -}}
29 | {{- end }}
30 |
31 | {{ define "__alert_list" }}
32 | {{ $timeFormat:="2006-01-02 15:04:05" }}
33 | {{ range . }}
34 | - 告警名称: {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
35 | 告警级别:{{ ` ` }}
36 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
37 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
38 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
39 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
40 | 实例: {{ template "__alertinstance" . }}
41 | {{- if .Labels.region }}
42 | 地域: {{ .Labels.region }}
43 | {{- end }}
44 | {{- if .Labels.zone }}
45 | 可用区: {{ .Labels.zone }}
46 | {{- end }}
47 | {{- if .Labels.product }}
48 | 产品: {{ .Labels.product }}
49 | {{- end }}
50 | {{- if .Labels.component }}
51 | 组件: {{ .Labels.component }}
52 | {{- end }}
53 | 告警状态: {{ if eq .Status "firing" }}🚨 {{ .Status | toUpper }}{{ else }}✅ {{ .Status | toUpper }}{{ end }}
54 | 开始: {{ .StartsAt.Format $timeFormat }}
55 | 结束: {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format $timeFormat }}{{ else }}Not End{{ end }}
56 | {{- if eq .Status "firing" }}
57 | 告警描述: {{ if .Annotations.description_cn }}{{ .Annotations.description_cn }}{{ else }}{{ .Annotations.description }}{{ end }}
58 | {{ end }}
59 | {{ end }}{{ end }}
60 |
61 | {{ define "__alert_summary" }}
62 | {{ range . -}}
63 | - {{ if eq .Status "firing" -}}
64 | {{ template "__alertinstance" . }}
65 | {{- else -}}
66 | {{ template "__alertinstance" . }}
67 | {{- end }}
68 | {{ end }}
69 | {{- end }}
70 |
71 | {{ define "prom.title" -}}
72 | {{ template "__subject" . -}}{{ end }}
73 |
74 | {{ define "prom.markdown" }}
75 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
76 | ---
77 | **摘要**
78 |
79 | {{ if gt (.Alerts.Firing|len ) 0 -}}
80 | **🚨 触发中告警 [{{ .Alerts.Firing|len }}]**
81 |
82 | {{ template "__alert_summary" .Alerts.Firing }}{{ end }}
83 |
84 | {{ if gt (.Alerts.Resolved|len) 0 -}}
85 | **✅ 已恢复告警 [{{ .Alerts.Resolved|len }}]**
86 |
87 | {{ template "__alert_summary" .Alerts.Resolved }}{{ end }}
88 |
89 | ---
90 | **详请**
91 |
92 | {{ if gt (.Alerts.Firing|len ) 0 -}}
93 | **🚨 触发中告警 [{{ .Alerts.Firing|len }}]**
94 | {{ template "__alert_list" .Alerts.Firing }}{{ end }}
95 |
96 | {{ if gt (.Alerts.Resolved|len) 0 -}}
97 | **✅ 已恢复告警 [{{ .Alerts.Resolved|len }}]**
98 | {{ template "__alert_list" .Alerts.Resolved }}{{ end }}
99 | {{ end }}
100 |
101 | {{ define "prom.text" }}
102 | {{ template "prom.markdown" . }}
103 | {{ end }}
104 |
--------------------------------------------------------------------------------
/pkg/models/templates/weixinapp.zh.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 |
4 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
7 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
8 |
9 | {{- ` • ` }}
10 |
11 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
12 |
13 | {{- ` • ` }}
14 |
15 | {{- if gt (.Alerts.Firing|len) 0 }}告警中:{{ .Alerts.Firing|len }}{{ end }}
16 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
17 | {{- if gt (.Alerts.Resolved|len) 0 }}已恢复:{{ .Alerts.Resolved|len }}{{ end }}
18 |
19 | {{ end }}
20 |
21 |
22 | {{ define "__externalURL" -}}
23 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
24 | {{- end }}
25 |
26 | {{ define "__alertinstance" -}}
27 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
28 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
29 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
30 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
31 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
32 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
33 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
34 | {{- end -}}
35 | {{- end }}
36 |
37 | {{ define "__alert_list" }}
38 | {{ range . }}
39 |
40 |
41 | > 告警名称 : {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
42 | 告警级别 :{{ ` ` }}
43 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
44 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
45 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
46 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
47 | 告警实例 : `{{ template "__alertinstance" . }}`
48 | {{- if .Labels.region }}
49 | 地域 : {{ .Labels.region }}
50 | {{- end }}
51 | {{- if .Labels.zone }}
52 | 可用区 : {{ .Labels.zone }}
53 | {{- end }}
54 | {{- if .Labels.product }}
55 | 产品 : {{ .Labels.product }}
56 | {{- end }}
57 | {{- if .Labels.component }}
58 | 组件 : {{ .Labels.component }}
59 | {{- end }}
60 | 告警状态 : {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }} {{ .Status | toUpper }}
61 | 开始时间 : {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
62 | 结束时间 : {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
63 | {{- if eq .Status "firing" }}
64 | 告警描述 : {{ if .Annotations.description_cn }}{{ .Annotations.description_cn }}{{ else }}{{ .Annotations.description }}{{ end }}
65 | {{- end }}
66 |
67 | {{ end }}
68 | {{ end }}
69 |
70 | {{ define "__alert_summary" -}}
71 | {{ range . }}
72 | {{ template "__alertinstance" . }}
73 | {{- end }}
74 | {{- end }}
75 |
76 | {{ define "prom.title" -}}
77 | {{ template "__subject" . }}
78 | {{ end }}
79 |
80 |
81 | {{ define "prom.markdown" }}
82 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
83 | ### 告警摘要
84 |
85 | {{ if gt (.Alerts.Firing|len ) 0 }}
86 | #### 🚨 触发中告警 [{{ .Alerts.Firing|len }}]
87 | {{ template "__alert_summary" .Alerts.Firing }}
88 | {{ end }}
89 |
90 |
91 | {{ if gt (.Alerts.Resolved|len) 0 }}
92 | #### ✅ 已恢复告警 [{{ .Alerts.Resolved|len }}]
93 | {{ template "__alert_summary" .Alerts.Resolved }}
94 | {{ end }}
95 |
96 | ### 告警详请
97 |
98 | {{ if gt (.Alerts.Firing|len ) 0 }}
99 | #### 🚨 触发中告警 [{{ .Alerts.Firing|len }}]
100 | {{ template "__alert_list" .Alerts.Firing }}
101 | {{ end }}
102 |
103 |
104 | {{ if gt (.Alerts.Resolved|len) 0 }}
105 | ##### ✅ 已恢复告警 [{{ .Alerts.Resolved|len }}]
106 | {{ template "__alert_list" .Alerts.Resolved }}
107 | {{ end }}
108 | {{ end }}
109 |
110 | {{ define "prom.text" }}
111 | {{ template "prom.markdown" . }}
112 | {{ end }}
113 |
--------------------------------------------------------------------------------
/pkg/models/templates/weixin.zh.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 |
4 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
7 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
8 |
9 | {{- ` • ` }}
10 |
11 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
12 |
13 | {{- ` • ` }}
14 |
15 | {{- if gt (.Alerts.Firing|len) 0 }}告警中:{{ .Alerts.Firing|len }}{{ end }}
16 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
17 | {{- if gt (.Alerts.Resolved|len) 0 }}已恢复:{{ .Alerts.Resolved|len }}{{ end }}
18 |
19 | {{ end }}
20 |
21 |
22 | {{ define "__externalURL" -}}
23 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
24 | {{- end }}
25 |
26 | {{ define "__alertinstance" -}}
27 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
28 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
29 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
30 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
31 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
32 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
33 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
34 | {{- end -}}
35 | {{- end }}
36 |
37 | {{ define "__alert_list" }}
38 | {{ range . }}
39 | > 告警名称 : {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
40 | >
41 | > 告警级别 :{{ ` ` }}
42 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
43 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
44 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
45 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
46 | >
47 | > 实例 : `{{ template "__alertinstance" . }}`
48 | >
49 | {{- if .Labels.region }}
50 | > 地域 : {{ .Labels.region }}
51 | >
52 | {{- end }}
53 | {{- if .Labels.zone }}
54 | > 可用区 : {{ .Labels.zone }}
55 | >
56 | {{- end }}
57 | {{- if .Labels.product }}
58 | > 产品 : {{ .Labels.product }}
59 | >
60 | {{- end }}
61 | {{- if .Labels.component }}
62 | > 组件 : {{ .Labels.component }}
63 | >
64 | {{- end }}
65 | > 告警状态 : {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }} {{ .Status | toUpper }}
66 | >
67 | > 开始时间 : {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
68 | >
69 | > 结束时间 : {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
70 | {{- if eq .Status "firing" }}
71 | >
72 | > 告警描述 : {{ if .Annotations.description_cn }}{{ .Annotations.description_cn }}{{ else }}{{ .Annotations.description }}{{ end }}
73 | {{- end }}
74 |
75 | {{ end }}
76 | {{ end }}
77 |
78 |
79 | {{ define "__alert_summary" -}}
80 | {{ range . }}
81 | {{ template "__alertinstance" . }}
82 | {{ end }}
83 | {{ end }}
84 |
85 | {{ define "prom.title" -}}
86 | {{ template "__subject" . }}
87 | {{ end }}
88 |
89 |
90 | {{ define "prom.markdown" }}
91 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
92 | #### 摘要
93 |
94 | {{ if gt (.Alerts.Firing|len ) 0 }}
95 | ##### 🚨 触发中告警 [{{ .Alerts.Firing|len }}]
96 | {{ template "__alert_summary" .Alerts.Firing }}
97 | {{ end }}
98 |
99 |
100 | {{ if gt (.Alerts.Resolved|len) 0 }}
101 | ##### ✅ 已恢复告警 [{{ .Alerts.Resolved|len }}]
102 | {{ template "__alert_summary" .Alerts.Resolved }}
103 | {{ end }}
104 |
105 | #### 详请
106 |
107 | {{ if gt (.Alerts.Firing|len ) 0 }}
108 | ##### 🚨 触发中告警 [{{ .Alerts.Firing|len }}]
109 | {{ template "__alert_list" .Alerts.Firing }}
110 | {{ end }}
111 |
112 |
113 | {{ if gt (.Alerts.Resolved|len) 0 }}
114 | ##### ✅ 已恢复告警 [{{ .Alerts.Resolved|len }}]
115 | {{ template "__alert_list" .Alerts.Resolved }}
116 | {{ end }}
117 | {{ end }}
118 |
119 | {{ define "prom.text" }}
120 | {{ template "prom.markdown" . }}
121 | {{ end }}
122 |
--------------------------------------------------------------------------------
/pkg/models/templates/feishu.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
4 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
7 | {{- ` • ` }}
8 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
9 | {{- ` • ` }}
10 | {{- if gt (.Alerts.Firing|len) 0 }}Firing:{{ .Alerts.Firing|len }}{{ end }}
11 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
12 | {{- if gt (.Alerts.Resolved|len) 0 }}Resolved:{{ .Alerts.Resolved|len }}{{ end }}
13 | {{ end }}
14 |
15 |
16 | {{ define "__externalURL" -}}
17 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
18 | {{- end }}
19 |
20 | {{ define "__alertinstance" -}}
21 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
22 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
23 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
24 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
25 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
26 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
27 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
28 | {{- end -}}
29 | {{- end }}
30 |
31 | {{ define "__alert_list" }}
32 | {{ $timeFormat:="2006-01-02 15:04:05" }}
33 | {{ range . }}
34 | - **Alert Name**: {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
35 | **Alert Level**:{{ ` ` }}
36 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
37 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
38 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
39 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
40 | **Instance**: {{ template "__alertinstance" . }}
41 | {{- if .Labels.region }}
42 | **Region**: {{ .Labels.region }}
43 | {{- end }}
44 | {{- if .Labels.zone }}
45 | **Zone**: {{ .Labels.zone }}
46 | {{- end }}
47 | {{- if .Labels.product }}
48 | **Product**: {{ .Labels.product }}
49 | {{- end }}
50 | {{- if .Labels.component }}
51 | **Component**: {{ .Labels.component }}
52 | {{- end }}
53 | **Alert Status**: {{ if eq .Status "firing" }}🚨 {{ .Status | toUpper }}{{ else }}✅ {{ .Status | toUpper }}{{ end }}
54 | **Start At**: {{ .StartsAt.Format $timeFormat }}
55 | **End At**: {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format $timeFormat }}{{ else }}Not End{{ end }}
56 | {{- if eq .Status "firing" }}
57 | **Description**: {{ if .Annotations.description_en }}{{ .Annotations.description_en }}{{ else }}{{ .Annotations.description }}{{ end }}
58 | {{ end }}
59 | {{ end }}{{ end }}
60 |
61 | {{ define "__alert_summary" }}
62 | {{ range . -}}
63 | - {{ if eq .Status "firing" -}}
64 | {{ template "__alertinstance" . }}
65 | {{- else -}}
66 | {{ template "__alertinstance" . }}
67 | {{- end }}
68 | {{ end }}
69 | {{- end }}
70 |
71 | {{ define "prom.title" -}}
72 | {{ template "__subject" . -}}{{ end }}
73 |
74 | {{ define "prom.markdown" }}
75 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
76 | ---
77 | **Summary**
78 |
79 | {{ if gt (.Alerts.Firing|len ) 0 -}}
80 | **🚨 Firing [{{ .Alerts.Firing|len }}] alerts**
81 |
82 | {{ template "__alert_summary" .Alerts.Firing }}{{ end }}
83 |
84 | {{ if gt (.Alerts.Resolved|len) 0 -}}
85 | **✅ Resolved [{{ .Alerts.Resolved|len }}] alerts**
86 |
87 | {{ template "__alert_summary" .Alerts.Resolved }}{{ end }}
88 |
89 | ---
90 | **Detail**
91 |
92 | {{ if gt (.Alerts.Firing|len ) 0 -}}
93 | **🚨 Firing [{{ .Alerts.Firing|len }}] alerts**
94 | {{ template "__alert_list" .Alerts.Firing }}{{ end }}
95 |
96 | {{ if gt (.Alerts.Resolved|len) 0 -}}
97 | **✅ Resolved [{{ .Alerts.Resolved|len }}] alerts**
98 | {{ template "__alert_list" .Alerts.Resolved }}{{ end }}
99 | {{ end }}
100 |
101 | {{ define "prom.text" }}
102 | {{ template "prom.markdown" . }}
103 | {{ end }}
104 |
--------------------------------------------------------------------------------
/pkg/models/templates/weixin.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 |
4 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
7 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
8 |
9 | {{- ` • ` }}
10 |
11 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
12 |
13 | {{- ` • ` }}
14 |
15 | {{- if gt (.Alerts.Firing|len) 0 }}Firing:{{ .Alerts.Firing|len }}{{ end }}
16 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
17 | {{- if gt (.Alerts.Resolved|len) 0 }}Resolved:{{ .Alerts.Resolved|len }}{{ end }}
18 |
19 | {{ end }}
20 |
21 |
22 | {{ define "__externalURL" -}}
23 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
24 | {{- end }}
25 |
26 | {{ define "__alertinstance" -}}
27 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
28 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
29 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
30 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
31 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
32 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
33 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
34 | {{- end -}}
35 | {{- end }}
36 |
37 | {{ define "__alert_list" }}
38 | {{ range . }}
39 | > Alert Name : {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
40 | >
41 | > Alert Level :{{ ` ` }}
42 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
43 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
44 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
45 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
46 | >
47 | > Instance : `{{ template "__alertinstance" . }}`
48 | >
49 | {{- if .Labels.region }}
50 | > Region : {{ .Labels.region }}
51 | >
52 | {{- end }}
53 | {{- if .Labels.zone }}
54 | > Zone : {{ .Labels.zone }}
55 | >
56 | {{- end }}
57 | {{- if .Labels.product }}
58 | > Product : {{ .Labels.product }}
59 | >
60 | {{- end }}
61 | {{- if .Labels.component }}
62 | > Component : {{ .Labels.component }}
63 | >
64 | {{- end }}
65 | > Alert Status : {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }} {{ .Status | toUpper }}
66 | >
67 | > Start At : {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
68 | >
69 | > End At : {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
70 | {{- if eq .Status "firing" }}
71 | >
72 | > Description : {{ if .Annotations.description_en }}{{ .Annotations.description_en }}{{ else }}{{ .Annotations.description }}{{ end }}
73 | {{- end }}
74 |
75 | {{ end }}
76 | {{ end }}
77 |
78 |
79 | {{ define "__alert_summary" -}}
80 | {{ range . }}
81 | {{ template "__alertinstance" . }}
82 | {{ end }}
83 | {{ end }}
84 |
85 | {{ define "prom.title" -}}
86 | {{ template "__subject" . }}
87 | {{ end }}
88 |
89 |
90 | {{ define "prom.markdown" }}
91 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
92 | #### Summary
93 |
94 | {{ if gt (.Alerts.Firing|len ) 0 }}
95 | ##### 🚨 Firing [{{ .Alerts.Firing|len }}] alerts
96 | {{ template "__alert_summary" .Alerts.Firing }}
97 | {{ end }}
98 |
99 |
100 | {{ if gt (.Alerts.Resolved|len) 0 }}
101 | ##### ✅ Resolved [{{ .Alerts.Resolved|len }}] alerts
102 | {{ template "__alert_summary" .Alerts.Resolved }}
103 | {{ end }}
104 |
105 | #### Detail
106 |
107 | {{ if gt (.Alerts.Firing|len ) 0 }}
108 | ##### 🚨 Firing [{{ .Alerts.Firing|len }}] alerts
109 | {{ template "__alert_list" .Alerts.Firing }}
110 | {{ end }}
111 |
112 |
113 | {{ if gt (.Alerts.Resolved|len) 0 }}
114 | ##### ✅ Resolved [{{ .Alerts.Resolved|len }}] alerts
115 | {{ template "__alert_list" .Alerts.Resolved }}
116 | {{ end }}
117 | {{ end }}
118 |
119 | {{ define "prom.text" }}
120 | {{ template "prom.markdown" . }}
121 | {{ end }}
122 |
--------------------------------------------------------------------------------
/pkg/models/templates/weixinapp.tmpl:
--------------------------------------------------------------------------------
1 | {{ define "__subject" -}}
2 | 【{{ .Signature }}】
3 |
4 | {{- if eq (index .Alerts 0).Labels.severity "ok" }} OK{{ end }}
5 | {{- if eq (index .Alerts 0).Labels.severity "info" }} INFO{{ end }}
6 | {{- if eq (index .Alerts 0).Labels.severity "warning" }} WARNING{{ end }}
7 | {{- if eq (index .Alerts 0).Labels.severity "critical" }} CRITICAL{{ end }}
8 |
9 | {{- ` • ` }}
10 |
11 | {{- if .CommonLabels.alertname_cn }}{{ .CommonLabels.alertname_cn }}{{ else if .CommonLabels.alertname_custom }}{{ .CommonLabels.alertname_custom }}{{ else if .CommonAnnotations.alertname }}{{ .CommonAnnotations.alertname }}{{ else }}{{ .GroupLabels.alertname }}{{ end }}
12 |
13 | {{- ` • ` }}
14 |
15 | {{- if gt (.Alerts.Firing|len) 0 }}Firing:{{ .Alerts.Firing|len }}{{ end }}
16 | {{- if and (gt (.Alerts.Firing|len) 0) (gt (.Alerts.Resolved|len) 0) }}/{{ end }}
17 | {{- if gt (.Alerts.Resolved|len) 0 }}Resolved:{{ .Alerts.Resolved|len }}{{ end }}
18 |
19 | {{ end }}
20 |
21 |
22 | {{ define "__externalURL" -}}
23 | {{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}
24 | {{- end }}
25 |
26 |
27 | {{ define "__alertinstance" -}}
28 | {{- if ne .Labels.alertinstance nil -}}{{ .Labels.alertinstance }}
29 | {{- else if ne .Labels.instance nil -}}{{ .Labels.instance }}
30 | {{- else if ne .Labels.node nil -}}{{ .Labels.node }}
31 | {{- else if ne .Labels.nodename nil -}}{{ .Labels.nodename }}
32 | {{- else if ne .Labels.host nil -}}{{ .Labels.host }}
33 | {{- else if ne .Labels.hostname nil -}}{{ .Labels.hostname }}
34 | {{- else if ne .Labels.ip nil -}}{{ .Labels.ip }}
35 | {{- end -}}
36 | {{- end }}
37 |
38 | {{ define "__alert_list" }}
39 | {{ range . }}
40 |
41 |
42 | > Alert Name : {{ if .Labels.alertname_cn }}{{ .Labels.alertname_cn }}{{ else if .Labels.alertname_custom }}{{ .Labels.alertname_custom }}{{ else if .Annotations.alertname }}{{ .Annotations.alertname }}{{ else }}{{ .Labels.alertname }}{{ end }}
43 | Alert Level :{{ ` ` }}
44 | {{- if eq .Labels.severity "ok" }}OK{{ end -}}
45 | {{- if eq .Labels.severity "info" }}INFO{{ end -}}
46 | {{- if eq .Labels.severity "warning" }}WARNING{{ end -}}
47 | {{- if eq .Labels.severity "critical" }}CRITICAL{{ end }}
48 | Alert Instance : `{{ template "__alertinstance" . }}`
49 | {{- if .Labels.region }}
50 | Region : {{ .Labels.region }}
51 | {{- end }}
52 | {{- if .Labels.zone }}
53 | Zone : {{ .Labels.zone }}
54 | {{- end }}
55 | {{- if .Labels.product }}
56 | Product : {{ .Labels.product }}
57 | {{- end }}
58 | {{- if .Labels.component }}
59 | Component : {{ .Labels.component }}
60 | {{- end }}
61 | Alert Status : {{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }} {{ .Status | toUpper }}
62 | Start At : {{ .StartsAt.Format "2006-01-02T15:04:05Z07:00" }}
63 | End At : {{ if .EndsAt.After .StartsAt }}{{ .EndsAt.Format "2006-01-02T15:04:05Z07:00" }}{{ else }}Not End{{ end }}
64 | {{- if eq .Status "firing" }}
65 | Description : {{ if .Annotations.description_en }}{{ .Annotations.description_en }}{{ else }}{{ .Annotations.description }}{{ end }}
66 | {{- end }}
67 |
68 | {{ end }}
69 | {{ end }}
70 |
71 | {{ define "__alert_summary" -}}
72 | {{ range . }}
73 | {{ template "__alertinstance" . }}
74 | {{- end }}
75 | {{- end }}
76 |
77 | {{ define "prom.title" -}}
78 | {{ template "__subject" . }}
79 | {{ end }}
80 |
81 |
82 | {{ define "prom.markdown" }}
83 | {{ .MessageAt.Format "2006-01-02T15:04:05Z07:00" }}
84 |
85 | ### Alerts Summary
86 |
87 | {{ if gt (.Alerts.Firing|len ) 0 }}
88 | #### 🚨 Firing [{{ .Alerts.Firing|len }}] alerts
89 | {{ template "__alert_summary" .Alerts.Firing }}
90 | {{ end }}
91 |
92 |
93 | {{ if gt (.Alerts.Resolved|len) 0 }}
94 | #### ✅ Resolved [{{ .Alerts.Resolved|len }}] alerts
95 | {{ template "__alert_summary" .Alerts.Resolved }}
96 | {{ end }}
97 |
98 | ### Alerts Detail
99 |
100 | {{ if gt (.Alerts.Firing|len ) 0 }}
101 | #### 🚨 Firing [{{ .Alerts.Firing|len }}] alerts
102 | {{ template "__alert_list" .Alerts.Firing }}
103 | {{ end }}
104 |
105 |
106 | {{ if gt (.Alerts.Resolved|len) 0 }}
107 | #### ✅ Resolved [{{ .Alerts.Resolved|len }}] alerts
108 | {{ template "__alert_list" .Alerts.Resolved }}
109 | {{ end }}
110 | {{ end }}
111 |
112 | {{ define "prom.text" }}
113 | {{ template "prom.markdown" . }}
114 | {{ end }}
115 |
--------------------------------------------------------------------------------
/pkg/models/alert.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "time"
9 |
10 | "github.com/bougou/webhook-adapter/models"
11 | )
12 |
13 | // ref: https://prometheus.io/docs/alerting/latest/configuration/#webhook_config
14 | // The Alertmanager will send HTTP POST requests in the following JSON format to the configured endpoint:
15 | // {
16 | // "version": "4",
17 | // "groupKey": , // key identifying the group of alerts (e.g. to deduplicate)
18 | // "truncatedAlerts": , // how many alerts have been truncated due to "max_alerts"
19 | // "status": "",
20 | // "receiver": ,
21 | // "groupLabels":