Public source validation / validate (push) Failing after 3m8s
100 lines
5.1 KiB
Go
100 lines
5.1 KiB
Go
package alert
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
func Unacknowledge(current Snapshot, actor string, at time.Time) (TransitionResult, error) {
|
|
current = current.normalized()
|
|
if actor == "" || len(actor) > 160 || at.IsZero() {
|
|
return TransitionResult{}, ErrInvalidObservation
|
|
}
|
|
if current.State != StateAcknowledged {
|
|
return TransitionResult{}, ErrStateConflict
|
|
}
|
|
result := current
|
|
result.State = StateFiring
|
|
result.RetainedState = StateFiring
|
|
result.AcknowledgedBy = ""
|
|
result.AcknowledgedAt = nil
|
|
result.Reason = "unacknowledged"
|
|
return finish(current, result, StateFiring, "unacknowledge"), nil
|
|
}
|
|
|
|
func (r StateRepository) AcknowledgeRevision(ctx context.Context, instanceID, actor, evaluationKey string, at time.Time, expectedRevision int64) (Instance, Occurrence, bool, error) {
|
|
return r.applyOperation(ctx, instanceID, actor, evaluationKey, at, expectedRevision, true)
|
|
}
|
|
|
|
func (r StateRepository) Unacknowledge(ctx context.Context, instanceID, actor, evaluationKey string, at time.Time, expectedRevision int64) (Instance, Occurrence, bool, error) {
|
|
return r.applyOperation(ctx, instanceID, actor, evaluationKey, at, expectedRevision, false)
|
|
}
|
|
|
|
func (r StateRepository) applyOperation(ctx context.Context, instanceID, actor, evaluationKey string, at time.Time, expectedRevision int64, acknowledge bool) (Instance, Occurrence, bool, error) {
|
|
if r.Pool == nil {
|
|
return Instance{}, Occurrence{}, false, ErrUnavailable
|
|
}
|
|
if instanceID == "" || actor == "" || len(actor) > 160 || evaluationKey == "" || len(evaluationKey) > 160 || at.IsZero() || expectedRevision < 1 {
|
|
return Instance{}, Occurrence{}, false, ErrInvalidObservation
|
|
}
|
|
tx, err := r.Pool.BeginTx(ctx, pgx.TxOptions{})
|
|
if err != nil {
|
|
return Instance{}, Occurrence{}, false, fmt.Errorf("begin alert operation: %w", err)
|
|
}
|
|
defer func() { _ = tx.Rollback(ctx) }()
|
|
var current Instance
|
|
if err := scanInstance(tx.QueryRow(ctx, `SELECT id,rule_id,rule_version_id,fingerprint,COALESCE(entity_id::text,''),current_state,retained_state,active_since,recovery_since,cooldown_until,last_evaluated_at,last_known_at,last_value,reason,source_health,COALESCE(acknowledged_by,''),acknowledged_at,revision,created_at,updated_at FROM alert_instances WHERE id=$1 FOR UPDATE`, instanceID), ¤t); errors.Is(err, ErrInstanceNotFound) {
|
|
return Instance{}, Occurrence{}, false, ErrInstanceNotFound
|
|
} else if err != nil {
|
|
return Instance{}, Occurrence{}, false, err
|
|
}
|
|
if occurrence, err := scanOccurrence(tx.QueryRow(ctx, `SELECT id,instance_id,evaluation_key,event_type,from_state,to_state,observed_at,value,reason,source_health,created_at FROM alert_occurrences WHERE instance_id=$1 AND evaluation_key=$2`, instanceID, evaluationKey)); err == nil {
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return Instance{}, Occurrence{}, false, fmt.Errorf("commit idempotent alert operation: %w", err)
|
|
}
|
|
return current, occurrence, true, nil
|
|
} else if !errors.Is(err, pgx.ErrNoRows) {
|
|
return Instance{}, Occurrence{}, false, err
|
|
}
|
|
if current.Revision != expectedRevision {
|
|
return Instance{}, Occurrence{}, false, ErrRevisionConflict
|
|
}
|
|
var transition TransitionResult
|
|
if acknowledge {
|
|
transition, err = Acknowledge(snapshotFromInstance(current), actor, at.UTC())
|
|
} else {
|
|
transition, err = Unacknowledge(snapshotFromInstance(current), actor, at.UTC())
|
|
}
|
|
if err != nil {
|
|
return Instance{}, Occurrence{}, false, err
|
|
}
|
|
valueJSON, err := boundedJSON(current.LastValue, 128<<10)
|
|
if err != nil {
|
|
return Instance{}, Occurrence{}, false, err
|
|
}
|
|
healthJSON, err := boundedJSON(nonNilMap(current.SourceHealth), 64<<10)
|
|
if err != nil {
|
|
return Instance{}, Occurrence{}, false, err
|
|
}
|
|
acknowledgedBy := nullableText(transition.Snapshot.AcknowledgedBy)
|
|
acknowledgedAt := transition.Snapshot.AcknowledgedAt
|
|
if _, err := tx.Exec(ctx, `UPDATE alert_instances SET current_state=$1,retained_state=$2,reason=$3,acknowledged_by=$4,acknowledged_at=$5,revision=revision+1,updated_at=now() WHERE id=$6 AND revision=$7`, transition.Snapshot.State, transition.Snapshot.RetainedState, transition.Snapshot.Reason, acknowledgedBy, acknowledgedAt, instanceID, expectedRevision); err != nil {
|
|
return Instance{}, Occurrence{}, false, mapStateError(fmt.Errorf("update alert operation: %w", err))
|
|
}
|
|
occurrence, err := insertOccurrence(ctx, tx, instanceID, evaluationKey, transition, Observation{ObservedAt: at.UTC(), Reason: transition.Snapshot.Reason, Value: current.LastValue}, valueJSON, healthJSON)
|
|
if err != nil {
|
|
return Instance{}, Occurrence{}, false, err
|
|
}
|
|
if err := scanInstance(tx.QueryRow(ctx, `SELECT id,rule_id,rule_version_id,fingerprint,COALESCE(entity_id::text,''),current_state,retained_state,active_since,recovery_since,cooldown_until,last_evaluated_at,last_known_at,last_value,reason,source_health,COALESCE(acknowledged_by,''),acknowledged_at,revision,created_at,updated_at FROM alert_instances WHERE id=$1`, instanceID), ¤t); err != nil {
|
|
return Instance{}, Occurrence{}, false, err
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return Instance{}, Occurrence{}, false, fmt.Errorf("commit alert operation: %w", err)
|
|
}
|
|
return current, occurrence, false, nil
|
|
}
|