| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package prober
- import (
- "bytes"
- "encoding/json"
- "errors"
- "expvar"
- "fmt"
- "io"
- "net/http"
- "os"
- "strings"
- "time"
- "github.com/google/uuid"
- "tailscale.com/util/httpm"
- )
- var (
- alertGenerated = expvar.NewInt("alert_generated")
- alertFailed = expvar.NewInt("alert_failed")
- warningGenerated = expvar.NewInt("warning_generated")
- warningFailed = expvar.NewInt("warning_failed")
- )
- // SendAlert sends an alert to the incident response system, to
- // page a human responder immediately.
- // summary should be short and state the nature of the emergency.
- // details can be longer, up to 29 KBytes.
- func SendAlert(summary, details string) error {
- type squadcastAlert struct {
- Message string `json:"message"`
- Description string `json:"description"`
- Tags map[string]string `json:"tags,omitempty"`
- Status string `json:"status"`
- EventId string `json:"event_id"`
- }
- sqa := squadcastAlert{
- Message: summary,
- Description: details,
- Tags: map[string]string{"severity": "critical"},
- Status: "trigger",
- EventId: uuid.New().String(),
- }
- sqBody, err := json.Marshal(sqa)
- if err != nil {
- alertFailed.Add(1)
- return fmt.Errorf("encoding alert payload: %w", err)
- }
- webhookUrl := os.Getenv("SQUADCAST_WEBHOOK")
- if webhookUrl == "" {
- warningFailed.Add(1)
- return errors.New("no SQUADCAST_WEBHOOK configured")
- }
- req, err := http.NewRequest(httpm.POST, webhookUrl, bytes.NewBuffer(sqBody))
- if err != nil {
- alertFailed.Add(1)
- return err
- }
- req.Header.Set("Content-Type", "application/json")
- client := &http.Client{Timeout: 10 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- alertFailed.Add(1)
- return err
- }
- defer resp.Body.Close()
- if resp.StatusCode != 200 {
- alertFailed.Add(1)
- return errors.New(resp.Status)
- }
- body, _ := io.ReadAll(resp.Body)
- if string(body) != "ok" {
- alertFailed.Add(1)
- return errors.New("non-ok response returned from Squadcast")
- }
- alertGenerated.Add(1)
- return nil
- }
- // SendWarning will post a message to Slack.
- // details should be a description of the issue.
- func SendWarning(details string) error {
- webhookUrl := os.Getenv("SLACK_WEBHOOK")
- if webhookUrl == "" {
- warningFailed.Add(1)
- return errors.New("no SLACK_WEBHOOK configured")
- }
- type slackRequestBody struct {
- Text string `json:"text"`
- }
- slackBody, err := json.Marshal(slackRequestBody{Text: details})
- if err != nil {
- warningFailed.Add(1)
- return err
- }
- req, err := http.NewRequest("POST", webhookUrl, bytes.NewReader(slackBody))
- if err != nil {
- warningFailed.Add(1)
- return err
- }
- req.Header.Set("Content-Type", "application/json")
- client := &http.Client{Timeout: 10 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- warningFailed.Add(1)
- return err
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- warningFailed.Add(1)
- return errors.New(resp.Status)
- }
- body, _ := io.ReadAll(resp.Body)
- if s := strings.TrimSpace(string(body)); s != "ok" {
- warningFailed.Add(1)
- return errors.New("non-ok response returned from Slack")
- }
- warningGenerated.Add(1)
- return nil
- }
|