Skip to content

Commit

Permalink
feat(cloudevents-server): improve notify info (#98)
Browse files Browse the repository at this point in the history
## Improvements

1. avoid notify for failed task run when it's created by a pipeline run.
2. add more details about failed tasks and step details for failed
pipeline run.

## Tests

- [x] Tested with real lark IM.

## Effects

<img width="592" alt="image"
src="https://github.com/PingCAP-QE/ee-apps/assets/2574558/2b33b33b-d5c9-4437-ae89-8c5ce07f5547">
<img width="597" alt="image"
src="https://github.com/PingCAP-QE/ee-apps/assets/2574558/b7f75fed-6dcd-45bb-a490-858ff318b021">

Signed-off-by: wuhuizuo <wuhuizuo@126.com>
  • Loading branch information
wuhuizuo committed Mar 15, 2024
1 parent 2020429 commit a95ec9e
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 118 deletions.
149 changes: 149 additions & 0 deletions cloudevents-server/pkg/events/custom/tekton/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@ package tekton

import (
"encoding/json"
"fmt"
"strings"
"time"

cloudevents "github.com/cloudevents/sdk-go/v2"
larksdk "github.com/larksuite/oapi-sdk-go/v3"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
tektoncloudevent "github.com/tektoncd/pipeline/pkg/reconciler/events/cloudevent"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/pkg/apis"

"github.com/PingCAP-QE/ee-apps/cloudevents-server/pkg/config"
"github.com/PingCAP-QE/ee-apps/cloudevents-server/pkg/events/handler"
Expand Down Expand Up @@ -36,3 +45,143 @@ func getTriggerUser(run AnnotationsGetter) string {

return contextData[eventContextAnnotationInnerKeyUser]
}

// <dashboard base url>/#/namespaces/<namespace>/<run-type>s/<run-name>
// source: /apis///namespaces/<namespace>//<run-name>
// https://tekton.abc.com/tekton/apis/tekton.dev/v1beta1/namespaces/ee-cd/pipelineruns/auto-compose-multi-arch-image-run-g5hqv
//
// "source": "/apis///namespaces/ee-cd//build-package-tikv-tikv-linux-9bn55-build-binaries",
func newDetailURL(etype, source, baseURL string) string {
words := strings.Split(source, "/")
runName := words[len(words)-1]
runType := words[len(words)-2]
runNamespace := words[len(words)-3]

if runType == "" {
runType = strings.Split(etype, ".")[3] + "s"
}

return fmt.Sprintf("%s/#/namespaces/%s/%s/%s", baseURL, runNamespace, runType, runName)
}

func extractLarkInfosFromEvent(event cloudevents.Event, baseURL string) (*cardMessageInfos, error) {
var data tektoncloudevent.TektonCloudEventData
if err := event.DataAs(&data); err != nil {
return nil, err
}

ret := cardMessageInfos{
Title: newLarkTitle(event.Type(), event.Subject()),
TitleTemplate: larkCardHeaderTemplates[tektoncloudevent.TektonEventType(event.Type())],
ViewURL: newDetailURL(event.Type(), event.Source(), baseURL),
}

switch {
case data.PipelineRun != nil:
fillInfosWithPipelineRun(data.PipelineRun, &ret)
if event.Type() == string(tektoncloudevent.PipelineRunFailedEventV1) {
ret.FailedTasks = getFailedTasks(data.PipelineRun)
}
case data.TaskRun != nil:
fillInfosWithTaskRun(data.TaskRun, &ret)
if event.Type() == string(tektoncloudevent.TaskRunFailedEventV1) {
ret.StepStatuses = getStepStatuses(&data.TaskRun.Status)
}
case data.Run != nil:
fillInfosWithCustomRun(data.Run, &ret)
}

return &ret, nil
}

func fillInfosWithCustomRun(data *v1alpha1.Run, ret *cardMessageInfos) {
fillTimeFileds(ret, data.Status.StartTime, data.Status.CompletionTime)

for _, p := range data.Spec.Params {
v, _ := p.Value.MarshalJSON()
ret.Params = append(ret.Params, [2]string{p.Name, string(v)})
}
if results := data.Status.Results; len(results) > 0 {
var parts []string
for _, r := range results {
parts = append(parts, fmt.Sprintf("**%s**:", r.Name), r.Value, "---")
ret.Results = append(ret.Results, [2]string{r.Name, r.Value})

}
}
}

func fillInfosWithTaskRun(data *v1beta1.TaskRun, ret *cardMessageInfos) {
fillTimeFileds(ret, data.Status.StartTime, data.Status.CompletionTime)

for _, p := range data.Spec.Params {
v, _ := p.Value.MarshalJSON()
ret.Params = append(ret.Params, [2]string{p.Name, string(v)})
}
if data.Status.GetCondition(apis.ConditionSucceeded).IsFalse() {
ret.RerunURL = fmt.Sprintf("tkn -n %s task start %s --use-taskrun %s",
data.Namespace, data.Spec.TaskRef.Name, data.Name)
}
if results := data.Status.TaskRunResults; len(results) > 0 {
for _, r := range results {
v, _ := r.Value.MarshalJSON()
ret.Results = append(ret.Results, [2]string{r.Name, string(v)})
}
}

getStepStatuses(&data.Status)
}

func fillInfosWithPipelineRun(data *v1beta1.PipelineRun, ret *cardMessageInfos) {
fillTimeFileds(ret, data.Status.StartTime, data.Status.CompletionTime)

for _, p := range data.Spec.Params {
v, _ := p.Value.MarshalJSON()
ret.Params = append(ret.Params, [2]string{p.Name, string(v)})
}
if data.Status.GetCondition(apis.ConditionSucceeded).IsFalse() {
ret.RerunURL = fmt.Sprintf("tkn -n %s pipeline start %s --use-pipelinerun %s",
data.Namespace, data.Spec.PipelineRef.Name, data.Name)
}
if results := data.Status.PipelineResults; len(results) > 0 {
for _, r := range results {
ret.Results = append(ret.Results, [2]string{r.Name, r.Value.StringVal})
}
}

}

func getFailedTasks(data *v1beta1.PipelineRun) map[string][][2]string {
ret := make(map[string][][2]string)
for _, v := range data.Status.TaskRuns {
succeededCondition := v.Status.GetCondition(apis.ConditionSucceeded)
if !succeededCondition.IsTrue() {
ret[v.PipelineTaskName] = getStepStatuses(v.Status)
}
}

return ret
}

func getStepStatuses(status *v1beta1.TaskRunStatus) [][2]string {
var ret [][2]string
for _, s := range status.Steps {
if s.Terminated != nil {
ret = append(ret, [2]string{s.Name, s.Terminated.Reason})
}
}

return ret
}

func fillTimeFileds(ret *cardMessageInfos, startTime, endTime *metav1.Time) {
if startTime != nil {
ret.StartTime = startTime.Format(time.RFC3339)
}
if endTime != nil {
ret.EndTime = endTime.Format(time.RFC3339)
}
if startTime != nil && endTime != nil {
ret.TimeCost = time.Duration(endTime.UnixNano() - startTime.UnixNano()).String()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ func (h *pipelineRunHandler) Handle(event cloudevents.Event) cloudevents.Result

return sendLarkMessages(h.LarkClient, receivers, event, h.DashboardBaseURL)
default:
log.Debug().Str("ce-type", event.Type()).Msg("skip notifing for the event type.")
log.Debug().
Str("handler", "pipelineRunHandler").
Str("ce-type", event.Type()).
Msg("skip notifing for the event type.")
return cloudevents.ResultACK
}
}
15 changes: 12 additions & 3 deletions cloudevents-server/pkg/events/custom/tekton/handler_taskrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
cloudevents "github.com/cloudevents/sdk-go/v2"
lark "github.com/larksuite/oapi-sdk-go/v3"
"github.com/rs/zerolog/log"
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
tektoncloudevent "github.com/tektoncd/pipeline/pkg/reconciler/events/cloudevent"
)

Expand All @@ -34,6 +35,11 @@ func (h *taskRunHandler) Handle(event cloudevents.Event) cloudevents.Result {

switch event.Type() {
case string(tektoncloudevent.TaskRunFailedEventV1):
// Skip notify the taskrun when it created by a pipelineRun.
if data.TaskRun.Labels[pipeline.PipelineRunLabelKey] != "" {
break
}

var receivers []string
// send notify to the trigger user if it's existed, else send to the receivers configurated by type.
if receiver := getTriggerUser(data.TaskRun); receiver != "" {
Expand All @@ -48,8 +54,11 @@ func (h *taskRunHandler) Handle(event cloudevents.Event) cloudevents.Result {
Msg("send notification for the event type.")

return sendLarkMessages(h.LarkClient, receivers, event, h.DashboardBaseURL)
default:
log.Debug().Str("ce-type", event.Type()).Msg("skip notifing for the event type.")
return cloudevents.ResultACK
}

log.Debug().
Str("handler", "taskRunHandler").
Str("ce-type", event.Type()).
Msg("skip notifing for the event type.")
return cloudevents.ResultACK
}
99 changes: 0 additions & 99 deletions cloudevents-server/pkg/events/custom/tekton/lark.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"regexp"
"strings"
"text/template"
"time"

"github.com/Masterminds/sprig/v3"
cloudevents "github.com/cloudevents/sdk-go/v2"
Expand All @@ -19,8 +18,6 @@ import (
"github.com/rs/zerolog/log"
tektoncloudevent "github.com/tektoncd/pipeline/pkg/reconciler/events/cloudevent"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/pkg/apis"

_ "embed"
)
Expand Down Expand Up @@ -115,84 +112,6 @@ func newLarkMessages(receivers []string, event cloudevents.Event, detailBaseUrl
return reqs, nil
}

func extractLarkInfosFromEvent(event cloudevents.Event, baseURL string) (*cardMessageInfos, error) {
var data tektoncloudevent.TektonCloudEventData
if err := event.DataAs(&data); err != nil {
return nil, err
}

ret := cardMessageInfos{
Title: newLarkTitle(event.Type(), event.Subject()),
TitleTemplate: larkCardHeaderTemplates[tektoncloudevent.TektonEventType(event.Type())],
ViewURL: newDetailURL(event.Type(), event.Source(), baseURL),
}

var startTime, endTime *metav1.Time
switch {
case data.PipelineRun != nil:
startTime = data.PipelineRun.Status.StartTime
endTime = data.PipelineRun.Status.CompletionTime
for _, p := range data.PipelineRun.Spec.Params {
v, _ := p.Value.MarshalJSON()
ret.Params = append(ret.Params, [2]string{p.Name, string(v)})
}
if data.PipelineRun.Status.GetCondition(apis.ConditionSucceeded).IsFalse() {
ret.RerunURL = fmt.Sprintf("tkn -n %s pipeline start %s --use-pipelinerun %s",
data.PipelineRun.Namespace, data.PipelineRun.Spec.PipelineRef.Name, data.PipelineRun.Name)
}
if results := data.PipelineRun.Status.PipelineResults; len(results) > 0 {
for _, r := range results {
ret.Results = append(ret.Results, [2]string{r.Name, r.Value.StringVal})
}
}
case data.TaskRun != nil:
for _, p := range data.TaskRun.Spec.Params {
v, _ := p.Value.MarshalJSON()
ret.Params = append(ret.Params, [2]string{p.Name, string(v)})
}
startTime = data.TaskRun.Status.StartTime
endTime = data.TaskRun.Status.CompletionTime
if data.TaskRun.Status.GetCondition(apis.ConditionSucceeded).IsFalse() {
ret.RerunURL = fmt.Sprintf("tkn -n %s task start %s --use-taskrun %s",
data.TaskRun.Namespace, data.TaskRun.Spec.TaskRef.Name, data.TaskRun.Name)
}

if results := data.TaskRun.Status.TaskRunResults; len(results) > 0 {
for _, r := range results {
v, _ := r.Value.MarshalJSON()
ret.Results = append(ret.Results, [2]string{r.Name, string(v)})
}
}
case data.Run != nil:
startTime = data.Run.Status.StartTime
endTime = data.Run.Status.CompletionTime
for _, p := range data.Run.Spec.Params {
v, _ := p.Value.MarshalJSON()
ret.Params = append(ret.Params, [2]string{p.Name, string(v)})
}
if results := data.Run.Status.Results; len(results) > 0 {
var parts []string
for _, r := range results {
parts = append(parts, fmt.Sprintf("**%s**:", r.Name), r.Value, "---")
ret.Results = append(ret.Results, [2]string{r.Name, r.Value})

}
}
}

if startTime != nil {
ret.StartTime = startTime.Format(time.RFC3339)
}
if endTime != nil {
ret.EndTime = endTime.Format(time.RFC3339)
}
if startTime != nil && endTime != nil {
ret.TimeCost = time.Duration(endTime.UnixNano() - startTime.UnixNano()).String()
}

return &ret, nil
}

func newLarkCardWithGoTemplate(event cloudevents.Event, baseURL string) (string, error) {
infos, err := extractLarkInfosFromEvent(event, baseURL)
if err != nil {
Expand Down Expand Up @@ -232,21 +151,3 @@ func newLarkTitle(etype, subject string) string {

return fmt.Sprintf("%s [%s] %s is %s ", larkCardHeaderEmojis[tektoncloudevent.TektonEventType(etype)], runType, subject, runState)
}

// <dashboard base url>/#/namespaces/<namespace>/<run-type>s/<run-name>
// source: /apis///namespaces/<namespace>//<run-name>
// https://tekton.abc.com/tekton/apis/tekton.dev/v1beta1/namespaces/ee-cd/pipelineruns/auto-compose-multi-arch-image-run-g5hqv
//
// "source": "/apis///namespaces/ee-cd//build-package-tikv-tikv-linux-9bn55-build-binaries",
func newDetailURL(etype, source, baseURL string) string {
words := strings.Split(source, "/")
runName := words[len(words)-1]
runType := words[len(words)-2]
runNamespace := words[len(words)-3]

if runType == "" {
runType = strings.Split(etype, ".")[3] + "s"
}

return fmt.Sprintf("%s/#/namespaces/%s/%s/%s", baseURL, runNamespace, runType, runName)
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,29 @@ elements:
{{- end }}
{{- end }}

{{- with .FailedTasks }}
- tag: hr
- tag: markdown
content: |-
**Failed tasks:**
{{- range $t, $ss := . }}
- {{ $t }}:
{{- range $ss }}
1. {{ index . 0 }}: {{ index . 1 }}
{{- end }}
{{- end }}
{{- end }}

{{- with .StepStatuses }}
- tag: hr
- tag: markdown
content: |-
**Steps:**
{{- range . }}
1. {{ index . 0 }}: {{ index . 1 }}
{{- end }}
{{- end }}

{{- with .Results }}
- tag: hr
- tag: markdown
Expand Down
6 changes: 4 additions & 2 deletions cloudevents-server/pkg/events/custom/tekton/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ type cardMessageInfos struct {
StartTime string
EndTime string
TimeCost string
Params [][2]string // key-value pairs.
Results [][2]string // Key-Value pairs.
Params [][2]string // key-value pairs.
Results [][2]string // Key-Value pairs.
StepStatuses [][2]string // name-status pairs.
FailedTasks map[string][][2]string // task id => step statuses.
}

var larkCardHeaderTemplates = map[tektoncloudevent.TektonEventType]string{
Expand Down
13 changes: 0 additions & 13 deletions cloudevents-server/pkg/events/custom/testcaserun/test.sh

This file was deleted.

0 comments on commit a95ec9e

Please sign in to comment.