mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed? Alert company slack when the main build fails after a PR merge ## Why? Help us combat our issues with flaky tests. ## How did you test it? - [ ] built - [X] run locally and tested manually - [ ] covered by existing tests - [ ] added new unit test(s) - [ ] added new functional test(s) ## Potential risks No, not running in production
130 lines
3.0 KiB
Go
130 lines
3.0 KiB
Go
package cinotify
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
"go.temporal.io/server/common/headers"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
FlagRunID = "run-id"
|
|
FlagSlackWebhook = "slack-webhook"
|
|
FlagDryRun = "dry-run"
|
|
)
|
|
|
|
// NewCliApp instantiates a new instance of the CLI application
|
|
func NewCliApp() *cli.App {
|
|
app := cli.NewApp()
|
|
app.Name = "ci-notify"
|
|
app.Usage = "Send Slack notifications for CI failures on main branch"
|
|
app.Version = headers.ServerVersion
|
|
|
|
app.Flags = []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: FlagRunID,
|
|
Usage: "GitHub Actions run ID",
|
|
Required: true,
|
|
EnvVars: []string{"GITHUB_RUN_ID"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: FlagSlackWebhook,
|
|
Usage: "Slack webhook URL",
|
|
Required: false, // Not required for dry-run
|
|
EnvVars: []string{"SLACK_WEBHOOK"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: FlagDryRun,
|
|
Usage: "Print message without sending to Slack",
|
|
Value: false,
|
|
},
|
|
}
|
|
|
|
app.Action = func(c *cli.Context) error {
|
|
return run(c)
|
|
}
|
|
|
|
return app
|
|
}
|
|
|
|
func run(c *cli.Context) error {
|
|
// Set up logger
|
|
logger, err := zap.NewProduction()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Failed to create logger: %v\n", err)
|
|
return nil // Don't fail CI if notification fails
|
|
}
|
|
defer func() { _ = logger.Sync() }()
|
|
|
|
runID := c.String(FlagRunID)
|
|
slackWebhook := c.String(FlagSlackWebhook)
|
|
dryRun := c.Bool(FlagDryRun)
|
|
|
|
logger.Info("Starting ci-notify",
|
|
zap.String("run_id", runID),
|
|
zap.Bool("dry_run", dryRun),
|
|
)
|
|
|
|
// Build failure report
|
|
report, err := BuildFailureReport(runID)
|
|
if err != nil {
|
|
logger.Error("Failed to build failure report", zap.Error(err))
|
|
// Don't fail CI if notification fails
|
|
return nil
|
|
}
|
|
|
|
logger.Info("Built failure report",
|
|
zap.String("workflow", report.Workflow.Name),
|
|
zap.String("sha", report.Commit.ShortSHA),
|
|
zap.String("author", report.Commit.Author),
|
|
zap.Int("failed_jobs", len(report.FailedJobs)),
|
|
zap.Int("total_jobs", report.TotalJobs),
|
|
)
|
|
|
|
// Handle dry-run mode
|
|
if dryRun {
|
|
logger.Info("Dry-run mode: printing message to stdout")
|
|
fmt.Println(FormatMessageForDebug(report))
|
|
fmt.Println("\n--- Slack JSON Payload ---")
|
|
message := BuildFailureMessage(report)
|
|
payload, err := marshalIndent(message)
|
|
if err != nil {
|
|
logger.Error("Failed to marshal message for display", zap.Error(err))
|
|
return nil
|
|
}
|
|
fmt.Println(payload)
|
|
return nil
|
|
}
|
|
|
|
// Validate webhook URL
|
|
if slackWebhook == "" {
|
|
logger.Error("Slack webhook URL is required when not in dry-run mode")
|
|
// Don't fail CI if notification fails
|
|
return nil
|
|
}
|
|
|
|
// Build and send Slack message
|
|
message := BuildFailureMessage(report)
|
|
|
|
logger.Info("Sending Slack notification")
|
|
if err := SendSlackMessage(slackWebhook, message); err != nil {
|
|
logger.Error("Failed to send Slack message", zap.Error(err))
|
|
// Don't fail CI if notification fails
|
|
return nil
|
|
}
|
|
|
|
logger.Info("Slack notification sent successfully")
|
|
return nil
|
|
}
|
|
|
|
func marshalIndent(v interface{}) (string, error) {
|
|
b, err := json.MarshalIndent(v, "", " ")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(b), nil
|
|
}
|