package monitoring import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "net/url" "strings" "time" ) // AlertmanagerClient is an HTTP client for the Alertmanager API v2. type AlertmanagerClient struct { baseURL string httpClient *http.Client } // NewAlertmanagerClient creates a new Alertmanager API client. func NewAlertmanagerClient(baseURL string) *AlertmanagerClient { return &AlertmanagerClient{ baseURL: strings.TrimRight(baseURL, "/"), httpClient: &http.Client{ Timeout: 30 * time.Second, }, } } // ListAlerts returns alerts matching the given filters. func (c *AlertmanagerClient) ListAlerts(ctx context.Context, filters AlertFilters) ([]Alert, error) { params := url.Values{} if filters.Active != nil { params.Set("active", fmt.Sprintf("%t", *filters.Active)) } if filters.Silenced != nil { params.Set("silenced", fmt.Sprintf("%t", *filters.Silenced)) } if filters.Inhibited != nil { params.Set("inhibited", fmt.Sprintf("%t", *filters.Inhibited)) } if filters.Unprocessed != nil { params.Set("unprocessed", fmt.Sprintf("%t", *filters.Unprocessed)) } if filters.Receiver != "" { params.Set("receiver", filters.Receiver) } for _, f := range filters.Filter { params.Add("filter", f) } u := c.baseURL + "/api/v2/alerts" if len(params) > 0 { u += "?" + params.Encode() } req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() //nolint:errcheck // cleanup on exit body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } var alerts []Alert if err := json.Unmarshal(body, &alerts); err != nil { return nil, fmt.Errorf("failed to parse alerts: %w", err) } return alerts, nil } // ListSilences returns all silences. func (c *AlertmanagerClient) ListSilences(ctx context.Context) ([]Silence, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/api/v2/silences", nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() //nolint:errcheck // cleanup on exit body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } var silences []Silence if err := json.Unmarshal(body, &silences); err != nil { return nil, fmt.Errorf("failed to parse silences: %w", err) } return silences, nil } // CreateSilence creates a new silence and returns the silence ID. func (c *AlertmanagerClient) CreateSilence(ctx context.Context, silence Silence) (string, error) { data, err := json.Marshal(silence) if err != nil { return "", fmt.Errorf("failed to marshal silence: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/v2/silences", bytes.NewReader(data)) if err != nil { return "", fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return "", fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() //nolint:errcheck // cleanup on exit body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } var result struct { SilenceID string `json:"silenceID"` } if err := json.Unmarshal(body, &result); err != nil { return "", fmt.Errorf("failed to parse response: %w", err) } return result.SilenceID, nil }