Public source validation / validate (push) Failing after 3m8s
105 lines
4.3 KiB
Go
105 lines
4.3 KiB
Go
package alert
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type Alert struct {
|
|
Instance
|
|
RuleName string `json:"ruleName"`
|
|
Severity string `json:"severity"`
|
|
EntityType string `json:"entityType,omitempty"`
|
|
EntityName string `json:"entityName,omitempty"`
|
|
Occurrences []Occurrence `json:"occurrences,omitempty"`
|
|
}
|
|
|
|
type AlertReader interface {
|
|
ListAlerts(context.Context, int, string) ([]Alert, error)
|
|
GetAlert(context.Context, string, int) (Alert, error)
|
|
}
|
|
|
|
func (r StateRepository) ListAlerts(ctx context.Context, limit int, state string) ([]Alert, error) {
|
|
if r.Pool == nil {
|
|
return nil, ErrUnavailable
|
|
}
|
|
if limit < 1 || limit > 100 {
|
|
return nil, errors.New("alert limit is invalid")
|
|
}
|
|
if state != "" && !validState(State(state)) {
|
|
return nil, errors.New("alert state filter is invalid")
|
|
}
|
|
rows, err := r.Pool.Query(ctx, `SELECT i.id,i.rule_id,i.rule_version_id,i.fingerprint,COALESCE(i.entity_id::text,''),i.current_state,i.retained_state,i.active_since,i.recovery_since,i.cooldown_until,i.last_evaluated_at,i.last_known_at,i.last_value,i.reason,i.source_health,COALESCE(i.acknowledged_by,''),i.acknowledged_at,i.revision,i.created_at,i.updated_at,r.name,r.severity,COALESCE(e.entity_type,''),COALESCE(e.display_name,'') FROM alert_instances i JOIN alert_rules r ON r.id=i.rule_id LEFT JOIN entities e ON e.id=i.entity_id WHERE (($2='' AND i.current_state <> 'inactive') OR ($2<>'' AND i.current_state=$2)) ORDER BY CASE i.current_state WHEN 'firing' THEN 1 WHEN 'acknowledged' THEN 2 WHEN 'pending' THEN 3 WHEN 'unknown' THEN 4 WHEN 'resolved' THEN 5 ELSE 6 END,i.updated_at DESC,i.id ASC LIMIT $1`, limit, state)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list alerts: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
items := make([]Alert, 0, limit)
|
|
for rows.Next() {
|
|
item, err := scanAlert(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate alerts: %w", err)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (r StateRepository) GetAlert(ctx context.Context, id string, occurrenceLimit int) (Alert, error) {
|
|
if r.Pool == nil {
|
|
return Alert{}, ErrUnavailable
|
|
}
|
|
if strings.TrimSpace(id) == "" {
|
|
return Alert{}, ErrInstanceNotFound
|
|
}
|
|
if occurrenceLimit < 1 || occurrenceLimit > 500 {
|
|
return Alert{}, errors.New("alert occurrence limit is invalid")
|
|
}
|
|
row := r.Pool.QueryRow(ctx, `SELECT i.id,i.rule_id,i.rule_version_id,i.fingerprint,COALESCE(i.entity_id::text,''),i.current_state,i.retained_state,i.active_since,i.recovery_since,i.cooldown_until,i.last_evaluated_at,i.last_known_at,i.last_value,i.reason,i.source_health,COALESCE(i.acknowledged_by,''),i.acknowledged_at,i.revision,i.created_at,i.updated_at,r.name,r.severity,COALESCE(e.entity_type,''),COALESCE(e.display_name,'') FROM alert_instances i JOIN alert_rules r ON r.id=i.rule_id LEFT JOIN entities e ON e.id=i.entity_id WHERE i.id=$1`, id)
|
|
item, err := scanAlert(row)
|
|
if errors.Is(err, ErrInstanceNotFound) || errors.Is(err, pgx.ErrNoRows) {
|
|
return Alert{}, ErrInstanceNotFound
|
|
}
|
|
if err != nil {
|
|
return Alert{}, fmt.Errorf("get alert: %w", err)
|
|
}
|
|
occurrences, err := r.ListOccurrences(ctx, id, occurrenceLimit)
|
|
if err != nil {
|
|
return Alert{}, err
|
|
}
|
|
item.Occurrences = occurrences
|
|
return item, nil
|
|
}
|
|
|
|
type alertRow interface{ Scan(...any) error }
|
|
|
|
func scanAlert(row alertRow) (Alert, error) {
|
|
var item Alert
|
|
var valueJSON, healthJSON []byte
|
|
var state, retained State
|
|
err := row.Scan(&item.ID, &item.RuleID, &item.RuleVersionID, &item.Fingerprint, &item.EntityID, &state, &retained, &item.ActiveSince, &item.RecoverySince, &item.CooldownUntil, &item.LastEvaluatedAt, &item.LastKnownAt, &valueJSON, &item.Reason, &healthJSON, &item.AcknowledgedBy, &item.AcknowledgedAt, &item.Revision, &item.CreatedAt, &item.UpdatedAt, &item.RuleName, &item.Severity, &item.EntityType, &item.EntityName)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return Alert{}, ErrInstanceNotFound
|
|
}
|
|
if err != nil {
|
|
return Alert{}, fmt.Errorf("scan alert: %w", err)
|
|
}
|
|
item.State, item.RetainedState = state, retained
|
|
item.LastValue, err = decodeJSON(valueJSON)
|
|
if err != nil {
|
|
return Alert{}, fmt.Errorf("decode alert value: %w", err)
|
|
}
|
|
item.SourceHealth, err = decodeMap(healthJSON)
|
|
if err != nil {
|
|
return Alert{}, fmt.Errorf("decode alert source health: %w", err)
|
|
}
|
|
return item, nil
|
|
}
|