-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: mohamed <[email protected]>
- Loading branch information
Showing
7 changed files
with
266 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
## CI Alerts | ||
|
||
a simple action to customize Slack alerts for CI failures using Slack [Webhooks](https://api.slack.com/messaging/webhooks). Simply, the action will send a message to a slack channel when a CI job fails in two cases: | ||
|
||
- If the job is triggered by a pull_request AND the author of the PR is in the `users_path` file, the author will be mentioned in the message. the message will be as the following: | ||
|
||
<img src="./example.png" width="400"/> | ||
|
||
- If the job is triggered by a push to the `master` branch, the message will be sent to the channel with mentioning the channel. | ||
|
||
## Usage Example | ||
```yaml | ||
name: Slack CI Alerts | ||
|
||
on: | ||
workflow_run: | ||
workflows: [Test, Build, ...] | ||
types: [completed] | ||
|
||
jobs: | ||
on-failure: | ||
runs-on: ubuntu-latest | ||
if: github.event.workflow_run.conclusion == 'failure' | ||
steps: | ||
- uses: actions/[email protected] | ||
- name: "Send Notification" | ||
uses: armadaproject/ci-alerts@v1 | ||
env: | ||
webhook: ${{ secrets.SLACK_WEBHOOK }} | ||
github_context: ${{ toJSON(github) }} | ||
users_path: ${{github.workspace}}/.github/gh-to-slackid | ||
``` | ||
### Environment Variables | ||
- `webhook`: the slack webhook url. [see more](https://api.slack.com/messaging/webhooks). | ||
- `github_context`: the github context. you can get it from `${{ toJSON(github) }}`. | ||
- `users_path`: the path to the file that contains the mapping from a github username to a slack [client id](https://api.slack.com/authentication/best-practices#client-id) so the author can be mentioned in the message. If the author doesn't exist in the file, no message will be sent to the slack channel. | ||
|
||
The format of the file should be as : | ||
``` | ||
github-username:slack-id | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
name: ci-alerts | ||
description: a workspace alerts for CI failures | ||
|
||
runs: | ||
using: 'composite' | ||
steps: | ||
- uses: actions/setup-go@v2 | ||
with: | ||
go-version: '1.20' | ||
- run: cd ${{ github.action_path }} && go mod tidy && go run main.go | ||
shell: bash |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module github.com/armadaproject/ci-alerts | ||
|
||
go 1.20 | ||
|
||
require github.com/antchfx/jsonquery v1.3.3 | ||
|
||
require ( | ||
github.com/antchfx/xpath v1.2.3 // indirect | ||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
github.com/antchfx/jsonquery v1.3.3 h1:zjZpbnZhYng3uOAbIfdNq81A9mMEeuDJeYIpeKpZ4es= | ||
github.com/antchfx/jsonquery v1.3.3/go.mod h1:1JG4DqRlRCHgVYDPY1ioYFAGSXGfWHzNgrbiGQHsWck= | ||
github.com/antchfx/xpath v1.2.3 h1:CCZWOzv5bAqjVv0offZ2LVgVYFbeldKQVuLNbViZdes= | ||
github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= | ||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
"log" | ||
"net/http" | ||
"os" | ||
"strings" | ||
|
||
"github.com/antchfx/jsonquery" | ||
) | ||
|
||
func main() { | ||
webhook := os.Getenv("webhook") | ||
github_context := os.Getenv("github_context") | ||
context, err := NewContext(webhook, github_context) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
message := buildMessage(context) | ||
body := strings.NewReader(message) | ||
_, err = http.Post(context.Webhook, "Content-type: application/json", body) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
type Context struct { | ||
Webhook string | ||
TriggeringEvent string | ||
Branch string | ||
Author string | ||
Commit string | ||
CommitUrl string | ||
WorkflowName string | ||
WorkflowUrl string | ||
JobsUrl string | ||
} | ||
|
||
func NewContext(Webhook, github_context string) (*Context, error) { | ||
jq, err := jsonquery.Parse(strings.NewReader(github_context)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
TriggeringEvent := jsonquery.FindOne(jq, "event/workflow_run/event").FirstChild.Data | ||
Branch := jsonquery.FindOne(jq, "event/workflow_run/head_branch").FirstChild.Data | ||
Author := jsonquery.FindOne(jq, "actor").FirstChild.Data | ||
Commit := jsonquery.FindOne(jq, "sha").FirstChild.Data | ||
repository := jsonquery.FindOne(jq, "repository").FirstChild.Data | ||
CommitUrl := fmt.Sprintf("https://github.com/%s/commit/%s", repository, Commit) | ||
WorkflowName := jsonquery.FindOne(jq, "event/workflow_run/name").FirstChild.Data | ||
WorkflowUrl := jsonquery.FindOne(jq, "event/workflow_run/html_url").FirstChild.Data | ||
JobsUrl := jsonquery.FindOne(jq, "event/workflow_run/jobs_url").FirstChild.Data | ||
|
||
return &Context{ | ||
Webhook, | ||
TriggeringEvent, | ||
Branch, | ||
Author, | ||
Commit, | ||
CommitUrl, | ||
WorkflowName, | ||
WorkflowUrl, | ||
JobsUrl, | ||
}, nil | ||
} | ||
|
||
func buildMessage(context *Context) string { | ||
title := fmt.Sprintf("CI Failed For Branch: %s", context.Branch) | ||
header := fmt.Sprintf(`{ | ||
"type" : "section", | ||
"text" : { | ||
"type": "mrkdwn", | ||
"text": "*%s*\n %s" | ||
} | ||
},`, title, getMention(context)) | ||
section := buildSection(context) | ||
message := fmt.Sprintf(`{"blocks" : [ %s ], "attachments":[{ "color": "#a60021", "blocks": [ %s ] }]}`, header, section) | ||
return message | ||
} | ||
|
||
func buildSection(context *Context) string { | ||
section := `{"type": "section", "fields":[` | ||
|
||
failed_action := fmt.Sprintf(` | ||
{ | ||
"type": "mrkdwn", | ||
"text": "*Failed Action*\n%s" | ||
}, | ||
`, context.WorkflowName) | ||
section += failed_action | ||
|
||
failed_job := fmt.Sprintf(` | ||
{ | ||
"type": "mrkdwn", | ||
"text": "*Failed Job*\n%s" | ||
}, | ||
`, getFailedJob(context.JobsUrl)) | ||
section += failed_job | ||
|
||
commit := fmt.Sprintf(` | ||
{ | ||
"type": "mrkdwn", | ||
"text": "*Commit*\n<%s|%s>" | ||
}, | ||
`, context.CommitUrl, context.Commit[:6]) | ||
section += commit | ||
|
||
action_url := fmt.Sprintf(` | ||
{ | ||
"type": "mrkdwn", | ||
"text": "*Workflow Url*\n%s" | ||
} | ||
`, context.WorkflowUrl) | ||
section += action_url | ||
|
||
section += `]}` | ||
return section | ||
} | ||
|
||
func getMention(context *Context) string { | ||
branch := strings.ToLower(context.Branch) | ||
if branch == "main" || branch == "master" { | ||
return "<!channel>" | ||
} else if context.TriggeringEvent == "pull_request" || context.TriggeringEvent == "push" { | ||
return getAuthorSlackID(context.Author) | ||
} | ||
return "" | ||
} | ||
|
||
func getAuthorSlackID(author string) string { | ||
path := os.Getenv("users_path") | ||
file, err := os.Open(path) | ||
if err != nil { | ||
return "" | ||
} | ||
defer file.Close() | ||
scanner := bufio.NewScanner(file) | ||
for scanner.Scan() { | ||
lineArr := strings.Split(scanner.Text(), ":") | ||
if len(lineArr) == 2 { | ||
if lineArr[0] == author { | ||
return fmt.Sprintf("<@%s>", lineArr[1]) | ||
} | ||
} | ||
} | ||
os.Exit(0) // exit the program, to prevent any notification from unknown authors' PRs | ||
return "" | ||
} | ||
|
||
func getFailedJob(url string) string { | ||
res, err := http.Get(url) | ||
if err != nil { | ||
fmt.Println(err) | ||
return "" | ||
} | ||
bodyBytes, err := io.ReadAll(res.Body) | ||
|
||
if err != nil { | ||
fmt.Println(err) | ||
return "" | ||
} | ||
|
||
jq, err := jsonquery.Parse(strings.NewReader(string(bodyBytes))) | ||
if err != nil { | ||
fmt.Println(err) | ||
return "" | ||
} | ||
|
||
jobs := jsonquery.FindOne(jq, "jobs").ChildNodes() | ||
for _, job := range jobs { | ||
status := job.SelectElement("conclusion").FirstChild.Data | ||
if status == "failure" { | ||
return job.SelectElement("name").FirstChild.Data | ||
} | ||
} | ||
return "" | ||
} |