watcher

package
v0.9.525 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package watcher parses Elasticsearch Watcher definitions (the exact PUT _watcher/watch body) and projects them onto Coremetry alert rules — Faz-1 of the operator requirement "birebir aynı Watcher JSON'ı Coremetry çalışabilsin" (v0.9.x).

Design contract:

  • Field names mirror the ES Watcher schema verbatim. Anything we don't interpret rides along as json.RawMessage — the FULL raw JSON is stored on the rule (AlertRule.WatcherJSON) so the definition round-trips byte-identical regardless of what this package understands.
  • Parse never rejects unknown fields; it records them so Validate can report "preserved but ignored".
  • Validate is a field-by-field mapping report (Supported / Partial / Unsupported + reason), the honest contract shown to the operator at import time.
  • ToRule is the executable projection: metric="watcher", comparator/threshold from condition.compare, window from the schedule interval, cooldown from throttle_period. Watches whose condition cannot be evaluated (script / never / non-hits.total compare) or that have no search input import DISABLED — the definition is kept, nothing silently fires wrong.

Pure package: no store, no network. The evaluator executes the projected rules via logstore.RawSearch.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NextFire added in v0.9.202

func NextFire(expr string, after time.Time) (time.Time, error)

NextFire parses expr and returns the next fire time strictly after `after`. Unparseable expressions and schedules with no future fire time (e.g. an exhausted year constraint) error.

func NormalizeDevTools

func NormalizeDevTools(raw []byte) []byte

NormalizeDevTools converts Kibana DevTools' triple-quoted string syntax ("""…raw body…""" — not valid JSON) into properly escaped JSON strings, so an operator can paste a watch straight out of the DevTools console (v0.9.x — review improvement b). Input without `"""` is returned UNCHANGED (byte-identical — the verbatim-storage contract holds); the import path stores the NORMALIZED form, since the evaluator re-parses the stored definition as JSON on every run. An unterminated `"""` is left as-is for Parse to reject with a pointer.

Types

type Action

type Action struct {
	Email   json.RawMessage `json:"email,omitempty"`
	Webhook json.RawMessage `json:"webhook,omitempty"`
	Slack   json.RawMessage `json:"slack,omitempty"`
	Logging json.RawMessage `json:"logging,omitempty"`
	Index   json.RawMessage `json:"index,omitempty"`
	// Per-action modifiers.
	ThrottlePeriod string          `json:"throttle_period,omitempty"`
	Condition      json.RawMessage `json:"condition,omitempty"`
	Transform      json.RawMessage `json:"transform,omitempty"`
	// Other lists action kinds this package doesn't model
	// (pagerduty, jira, …) — reported Unsupported.
	Other []string `json:"-"`
}

Action is one named entry under "actions". Kind payloads ride raw — Faz-1 never executes them (Coremetry's own notification channels fire on problem open/resolve); they're kept for round-trip + report.

func (*Action) UnmarshalJSON

func (a *Action) UnmarshalJSON(b []byte) error

type CompareClause

type CompareClause map[string]any

CompareClause is one {op: value} object, e.g. {"gte": 100}.

type CondKind added in v0.9.202

type CondKind int

CondKind classifies how the evaluator obtains the value it compares.

const (
	// CondAlways — no condition / always / empty compare: fires
	// whenever the search runs (hits.total >= 0).
	CondAlways CondKind = iota
	// CondHitsTotal — compare on ctx.payload.hits.total(.value).
	CondHitsTotal
	// CondAggValue — compare on a ctx.payload.aggregations.… path.
	CondAggValue
	// CondArrayCompare — array_compare over an aggregations array:
	// fires when ANY element matches (value = MAX of the matches).
	CondArrayCompare
)

type Condition

type Condition struct {
	Compare      map[string]CompareClause `json:"compare,omitempty"`
	ArrayCompare json.RawMessage          `json:"array_compare,omitempty"`
	Script       json.RawMessage          `json:"script,omitempty"`
	Always       bool                     `json:"-"`
	Never        bool                     `json:"-"`
}

Condition — compare on ctx.payload.hits.total is the executable subset; always maps to unconditional fire, never to a disabled rule; script / array_compare have no engine here.

func (*Condition) UnmarshalJSON

func (c *Condition) UnmarshalJSON(b []byte) error

UnmarshalJSON handles the presence-typed always/never members ({"always": {}}) alongside the object-valued kinds.

type ConditionSpec added in v0.9.202

type ConditionSpec struct {
	Kind       CondKind
	Path       string // CondAggValue: dotted payload path
	ArrayPath  string // CondArrayCompare: path to the array
	ItemPath   string // CondArrayCompare: per-element path ("" = the element itself)
	Comparator string
	Threshold  float64
}

ConditionSpec is the executable projection of the watch condition. Path/ArrayPath come back PAYLOAD-RELATIVE (the "ctx.payload." prefix stripped) so the evaluator can walk the RawSearchPayload result — {"hits":{"total":{"value":N}},"aggregations":{…}} — directly. Comparator is in the evaluator's set (> >= < <=).

type CronSchedule added in v0.9.202

type CronSchedule struct {
	// contains filtered or unexported fields
}

CronSchedule is a parsed Quartz expression in the supported subset.

func ParseCron added in v0.9.202

func ParseCron(expr string) (*CronSchedule, error)

ParseCron validates a Quartz cron expression against the supported subset and returns the parsed schedule. Errors carry the reason the Validate report shows the operator.

func (*CronSchedule) Next added in v0.9.202

func (c *CronSchedule) Next(after time.Time) time.Time

Next returns the first fire time strictly after `after` (UTC), or the zero time when none exists within the search horizon.

type Finding

type Finding struct {
	Field  string  `json:"field"`
	Status Support `json:"status"`
	Reason string  `json:"reason"`
}

Finding is one field's verdict with the reason an operator reads at import time.

type Input

type Input struct {
	Search *SearchInput    `json:"search,omitempty"`
	Simple json.RawMessage `json:"simple,omitempty"`
	HTTP   json.RawMessage `json:"http,omitempty"`
	Chain  json.RawMessage `json:"chain,omitempty"`
}

Input — search is the only kind Faz-1 executes; http / simple / chain are preserved raw and reported Unsupported.

type Report

type Report struct {
	Findings       []Finding `json:"findings"`
	Disabled       bool      `json:"disabled"`
	DisabledReason string    `json:"disabledReason,omitempty"`
}

Report is the whole-watch mapping verdict. Disabled means the projected rule cannot (or must not) run and imports with Enabled=false; the definition itself is always kept.

func ToRule

func ToRule(w *Watch, raw []byte) (chstore.AlertRule, Report, error)

ToRule projects a parsed watch onto an executable alert rule and returns the mapping report alongside. The caller (Faz-2 import API) assigns ID and, when metadata.name is absent, Name. raw is stored verbatim on the rule for round-trip + re-evaluation.

func Validate

func Validate(w *Watch) Report

Validate produces the field-by-field mapping report for a parsed watch. It never mutates the watch.

func (Report) HasUnsupported

func (r Report) HasUnsupported() bool

HasUnsupported reports whether any finding is Unsupported.

type Schedule

type Schedule struct {
	Interval string   `json:"interval,omitempty"`
	Cron     []string `json:"cron,omitempty"`
	Other    []string `json:"-"`
}

Schedule is the parsed trigger.schedule. Interval keeps the ES string form ("5m"); a numeric interval (seconds) is normalised to "Ns". Cron carries the expression(s) — string or array in ES. Other lists further schedule kinds present (hourly / daily / …).

func (*Schedule) HasTimezone added in v0.9.202

func (s *Schedule) HasTimezone() bool

HasTimezone — trigger.schedule bir timezone anahtarı taşıyor mu (ES 8.13+; UnmarshalJSON bilinmeyen anahtarları Other'a düşürür). v0.9.202: timezone'lu cron takvim yoluna ALINMAZ — UTC pacing ES'in tz-aware ateşlemesinden saatlerce kayar (kanıtlı 3h drift).

func (*Schedule) UnmarshalJSON

func (s *Schedule) UnmarshalJSON(b []byte) error

UnmarshalJSON tolerates the ES shapes: interval as string or number-of-seconds, cron as string or []string, plus any other schedule kind recorded by name.

type SearchInput

type SearchInput struct {
	Request SearchRequest   `json:"request"`
	Extract json.RawMessage `json:"extract,omitempty"`
	Timeout string          `json:"timeout,omitempty"`
}

type SearchRequest

type SearchRequest struct {
	Indices        indices         `json:"indices,omitempty"`
	Body           json.RawMessage `json:"body,omitempty"`
	Template       json.RawMessage `json:"template,omitempty"`
	SearchType     string          `json:"search_type,omitempty"`
	IndicesOptions json.RawMessage `json:"indices_options,omitempty"`
	// RestTotalHitsAsInt is a response-SHAPE flag (hits.total as a
	// bare int instead of {value, relation}); Coremetry issues the
	// search itself and reads hits.total.value directly, so this is
	// ignored by design — reported Supported, not scary-Partial.
	RestTotalHitsAsInt bool `json:"rest_total_hits_as_int,omitempty"`
}

SearchRequest carries the ES search verbatim: indices + body pass straight through to logstore.RawSearch (which injects the cost guards). Template / indices_options ride raw.

type Support

type Support string

Support classifies how faithfully one watcher field maps onto Coremetry's evaluator.

const (
	Supported   Support = "supported"
	Partial     Support = "partial"
	Unsupported Support = "unsupported"
)

type Trigger

type Trigger struct {
	Schedule *Schedule `json:"schedule,omitempty"`
}

Trigger — only the schedule trigger exists in ES today.

type Watch

type Watch struct {
	Trigger   *Trigger          `json:"trigger,omitempty"`
	Input     *Input            `json:"input,omitempty"`
	Condition *Condition        `json:"condition,omitempty"`
	Actions   map[string]Action `json:"actions,omitempty"`
	// ThrottlePeriod is the watch-level action throttle ("5m", "30s",
	// "1d"); the millis twin is the alternative ES serialisation.
	ThrottlePeriod         string          `json:"throttle_period,omitempty"`
	ThrottlePeriodInMillis int64           `json:"throttle_period_in_millis,omitempty"`
	Metadata               json.RawMessage `json:"metadata,omitempty"`
	Transform              json.RawMessage `json:"transform,omitempty"`
	// Unknown lists top-level keys this package doesn't model
	// (sorted). They are preserved via the stored raw JSON and
	// surfaced on the Validate report.
	Unknown []string `json:"-"`
}

Watch is the parsed ES watcher definition. Interpreted fields are typed; everything else is carried as raw JSON.

func Parse

func Parse(raw []byte) (*Watch, error)

Parse decodes an ES watcher body. Unknown top-level fields never fail the parse — they land in Watch.Unknown and the stored raw JSON preserves them byte-for-byte for round-trip.

func (*Watch) CalendarCron added in v0.9.202

func (w *Watch) CalendarCron() []string

CalendarCron returns the schedule's cron expressions when the watch paces via CALENDAR cron: cron-only schedule (an interval, parseable or not, keeps the Faz-1 interval path — mirrors IntervalSec priority), NOT the fixed-rate cron→interval subset (geriye uyum: those watches keep their Faz-1 pacing and report message), and every expression parseable by ParseCron. nil otherwise — the caller falls back to interval/default pacing.

func (*Watch) ConditionSpec added in v0.9.202

func (w *Watch) ConditionSpec() (ConditionSpec, bool)

ConditionSpec projects the condition onto the executable subset. ok=false means the condition cannot run (script / never / unsupported path / non-numeric value…) — Validate carries the operator-facing reason; this is the machine-facing twin.

func (*Watch) IntervalSec

func (w *Watch) IntervalSec() uint32

IntervalSec returns the schedule interval in seconds, clamped to Coremetry's 1m evaluator tick floor. A fixed-rate Quartz cron (cron→interval subset, v0.9.x) counts as an interval schedule. 0 = no parseable interval schedule (caller applies the 5m default).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL