Migration files restructured with shared helpers; verified byte-identical SQLite schema vs previous commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
49 lines
1.5 KiB
Go
49 lines
1.5 KiB
Go
package notify
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/v10networks/nmon/internal/store"
|
|
)
|
|
|
|
// HeaderIncident carries the incident ID on every webhook request.
|
|
const HeaderIncident = "X-Nmon-Incident"
|
|
|
|
// sendWebhook POSTs the stored (already redacted) payload.
|
|
//
|
|
// Outcome: 2xx is success. 408, 429 and 5xx are retried, as are network
|
|
// errors and timeouts. Every other status (4xx, and unfollowed 3xx) is a
|
|
// permanent failure: the receiver understood the request and refused it, so
|
|
// resending the same payload will not help. A destination denied by the
|
|
// outbound policy is also permanent.
|
|
func (q *Queue) sendWebhook(ctx context.Context, c *compiled, d store.Delivery) error {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.URL, strings.NewReader(d.Payload))
|
|
if err != nil {
|
|
return permanent{err}
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("User-Agent", "nmon")
|
|
for k, v := range c.Headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
req.Header.Set(HeaderIncident, d.IncidentID) // not overridable by config
|
|
resp, err := q.http.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = resp.Body.Close() }() // body is drained below
|
|
_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 64<<10))
|
|
switch s := resp.StatusCode; {
|
|
case s >= 200 && s < 300:
|
|
return nil
|
|
case s == http.StatusRequestTimeout || s == http.StatusTooManyRequests || s >= 500:
|
|
return fmt.Errorf("webhook returned HTTP %d", s)
|
|
default:
|
|
return permanent{fmt.Errorf("webhook returned HTTP %d", s)}
|
|
}
|
|
}
|