Skip to content

Commit

Permalink
feat(publisher): pretty the notify lark message
Browse files Browse the repository at this point in the history
Signed-off-by: wuhuizuo <[email protected]>
  • Loading branch information
wuhuizuo committed Nov 7, 2024
1 parent 495e6f0 commit c80106c
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 21 deletions.
46 changes: 46 additions & 0 deletions publisher/pkg/impl/lark.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package impl

import (
"bytes"
"encoding/json"
"text/template"

"github.com/Masterminds/sprig/v3"
"gopkg.in/yaml.v3"

_ "embed"
)

//go:embed lark_templates/tiup-publish-failure-notify.yaml.tmpl
var larkTemplateBytes string

func newLarkCardWithGoTemplate(infos any) (string, error) {
tmpl, err := template.New("lark").Funcs(sprig.FuncMap()).Parse(larkTemplateBytes)
if err != nil {
return "", err
}

tmplResult := new(bytes.Buffer)
if err := tmpl.Execute(tmplResult, infos); err != nil {
return "", err
}

values := make(map[string]interface{})
if err := yaml.Unmarshal(tmplResult.Bytes(), &values); err != nil {
return "", err
}

jsonBytes, err := json.Marshal(values)
if err != nil {
return "", err
}

return string(jsonBytes), nil
}

type failureNotifyInfo struct {
Title string
RerunCommands string
FailedMessage string
Params [][2]string // key-value pairs.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
msg_type: interactive
card:
config:
wide_screen_mode: true
elements:
{{- with .FailedMessage }}
- tag: markdown
content: |-
**Failed summary:**
```text
{{ indent 8 . }}
```
- tag: hr
{{- end }}
{{- with .Params }}
- tag: markdown
content: |-
**Params:**
{{- range . }}
- **{{ index . 0 }}:** {{ index . 1 }}
{{- end }}
{{- end }}

{{- with .RerunCommands }}
- tag: hr
- tag: markdown
content: |-
🔧 **Rerun:**

```BASH
{{ . }}
```
{{- end }}

header:
template: red # blue | wathet | turquoise | green | yellow | orange | red | carmine | violet | purple | indigo | grey
title:
content: {{ .Title }}
tag: plain_text
54 changes: 33 additions & 21 deletions publisher/pkg/impl/tiup_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package impl
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os"
Expand All @@ -20,9 +19,10 @@ type tiupWorker struct {
logger zerolog.Logger
redisClient redis.Cmdable
options struct {
LarkWebhookURL string
MirrorURL string
NightlyInterval time.Duration
LarkWebhookURL string
MirrorURL string
PublicServiceURL string
NightlyInterval time.Duration
}
}

Expand All @@ -41,6 +41,11 @@ func NewTiupWorker(logger *zerolog.Logger, redisClient redis.Cmdable, options ma
handler.options.NightlyInterval = nigthlyInterval
handler.options.MirrorURL = options["mirror_url"]
handler.options.LarkWebhookURL = options["lark_webhook_url"]
if options["public_service_url"] != "" {
handler.options.PublicServiceURL = options["public_service_url"]
} else {
handler.options.PublicServiceURL = "http://publisher-<env>-mirror.<namespace>.svc"
}

return &handler, nil
}
Expand All @@ -67,7 +72,7 @@ func (p *tiupWorker) Handle(event cloudevents.Event) cloudevents.Result {
p.redisClient.SetXX(context.Background(), event.ID(), PublishStateSuccess, redis.KeepTTL)
case cloudevents.IsNACK(result):
p.redisClient.SetXX(context.Background(), event.ID(), PublishStateFailed, redis.KeepTTL)
p.notifyLark(&data.Publish, result)
p.notifyLark(data, result)
default:
p.redisClient.SetXX(context.Background(), event.ID(), PublishStateCanceled, redis.KeepTTL)
}
Expand Down Expand Up @@ -136,34 +141,41 @@ func (p *tiupWorker) handle(data *PublishRequest) cloudevents.Result {
return cloudevents.ResultACK
}

func (p *tiupWorker) notifyLark(publishInfo *PublishInfo, err error) {
func (p *tiupWorker) notifyLark(req *PublishRequest, err error) {
if p.options.LarkWebhookURL == "" {
return
}

message := fmt.Sprintf("Failed to publish %s-%s @%s/%s platform to mirror %s: %v",
publishInfo.Name,
publishInfo.Version,
publishInfo.OS,
publishInfo.Arch,
p.options.MirrorURL,
err)

payload := map[string]interface{}{
"msg_type": "text",
"content": map[string]string{
"text": message,
rerunCmd := fmt.Sprintf(`go run %s --url %s tiup request-to-publish --body '{"artifact_url": "%s"}'`,
"github.com/PingCAP-QE/ee-apps/publisher/cmd/publisher-cli@main",
p.options.PublicServiceURL,
req.From.String(),
)

info := failureNotifyInfo{
Title: "TiUP Publish Failed",
FailedMessage: err.Error(),
RerunCommands: rerunCmd,
Params: [][2]string{
{"package", req.Publish.Name},
{"version", req.Publish.Version},
{"os", req.Publish.OS},
{"arch", req.Publish.Arch},
{"to-mirror", p.options.MirrorURL},
{"from", req.From.String()},
},
}

jsonPayload, err := json.Marshal(payload)
jsonPayload, err := newLarkCardWithGoTemplate(info)
if err != nil {
p.logger.Err(err).Msg("failed to marshal JSON payload")
p.logger.Err(err).Msg("failed to gen message payload")
return
}

resp, err := http.Post(p.options.LarkWebhookURL, "application/json", bytes.NewBuffer(jsonPayload))
resp, err := http.Post(p.options.LarkWebhookURL, "application/json", bytes.NewBufferString(jsonPayload))
if err != nil {
p.logger.Err(err).Msg("failed to send notification to Lark")
return
}
defer resp.Body.Close()

Expand Down
144 changes: 144 additions & 0 deletions publisher/pkg/impl/tiup_worker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package impl

import (
"errors"
"testing"
"time"

"github.com/go-redis/redis/v8"
"github.com/rs/zerolog"
)

func Test_tiupWorker_notifyLark(t *testing.T) {
t.Skipf("It is manually test case")
const testWebhookURL = "https://open.feishu.cn/open-apis/bot/v2/hook/<please-replace-it>"

type fields struct {
logger zerolog.Logger
redisClient redis.Cmdable
options struct {
LarkWebhookURL string
MirrorURL string
PublicServiceURL string
NightlyInterval time.Duration
}
}
type args struct {
req *PublishRequest
err error
}
tests := []struct {
name string
fields fields
args args
}{
{
name: "Empty webhook URL",
fields: fields{
logger: zerolog.New(nil),
options: struct {
LarkWebhookURL string
MirrorURL string
PublicServiceURL string
NightlyInterval time.Duration
}{
LarkWebhookURL: "",
},
},
args: args{
req: &PublishRequest{
Publish: PublishInfo{
Name: "test-package",
Version: "v1.0.0",
},
},
err: errors.New("test error"),
},
},
{
name: "Valid notification with all fields",
fields: fields{
logger: zerolog.New(nil),
options: struct {
LarkWebhookURL string
MirrorURL string
PublicServiceURL string
NightlyInterval time.Duration
}{
LarkWebhookURL: testWebhookURL,
MirrorURL: "https://test-mirror.com",
PublicServiceURL: "https://test-service.com",
},
},
args: args{
req: &PublishRequest{
From: From{
Type: FromTypeOci,
Oci: &FromOci{
Repo: "test-repo",
Tag: "test-tag",
},
},
Publish: PublishInfo{
Name: "test-package",
Version: "v1.0.0",
OS: "linux",
Arch: "amd64",
},
},
err: errors.New("publish failed"),
},
},
{
name: "Invalid webhook URL",
fields: fields{
logger: zerolog.New(nil),
options: struct {
LarkWebhookURL string
MirrorURL string
PublicServiceURL string
NightlyInterval time.Duration
}{
LarkWebhookURL: "invalid-url",
},
},
args: args{
req: &PublishRequest{
Publish: PublishInfo{
Name: "test-package",
Version: "v1.0.0",
},
},
err: errors.New("test error"),
},
},
{
name: "Missing publish info",
fields: fields{
logger: zerolog.New(nil),
options: struct {
LarkWebhookURL string
MirrorURL string
PublicServiceURL string
NightlyInterval time.Duration
}{
LarkWebhookURL: testWebhookURL,
},
},
args: args{
req: &PublishRequest{},
err: errors.New("missing publish info"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &tiupWorker{
logger: tt.fields.logger,
redisClient: tt.fields.redisClient,
options: tt.fields.options,
}
p.notifyLark(tt.args.req, tt.args.err)
})
}
}
15 changes: 15 additions & 0 deletions publisher/pkg/impl/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ type From struct {
HTTP *FromHTTP `json:"http,omitempty"`
}

func (f From) String() string {
switch f.Type {
case FromTypeOci:
return f.Oci.String()
case FromTypeHTTP:
return f.HTTP.URL
default:
return ""
}
}

type PublishInfo struct {
Name string `json:"name,omitempty"` // tiup pkg name or component name for fileserver
OS string `json:"os,omitempty"` // ignore for `EventTypeFsPublishRequest`
Expand All @@ -50,6 +61,10 @@ type FromOci struct {
File string `json:"file,omitempty"`
}

func (f FromOci) String() string {
return f.Repo + ":" + f.Tag + "#" + f.File
}

type FromHTTP struct {
URL string `json:"url,omitempty"`
}
Expand Down

0 comments on commit c80106c

Please sign in to comment.