diff --git a/grafana-alertcheck/.changeset/v0.1.6.md b/grafana-alertcheck/.changeset/v0.1.6.md new file mode 100644 index 000000000..5a8772094 --- /dev/null +++ b/grafana-alertcheck/.changeset/v0.1.6.md @@ -0,0 +1 @@ +- A no-match now states that datasource-managed alert rules cannot be observed and are not supported, and the rule count names its scope (`N grafana-managed rules available`). The Grafana-managed ruler endpoint this tool queries does not return datasource-managed rules, so a bare `no rule matched` previously read as "no such rule exists". diff --git a/grafana-alertcheck/docs/reference/cli.md b/grafana-alertcheck/docs/reference/cli.md index 46574f398..947ec7120 100644 --- a/grafana-alertcheck/docs/reference/cli.md +++ b/grafana-alertcheck/docs/reference/cli.md @@ -105,7 +105,7 @@ Alert names take one of four forms: | `Platform/api/HighErrorRate` | Folder + group + title (may still be ambiguous; use `uid:` for guaranteed uniqueness) | | `uid:abc123` | Exact uid (present on both endpoints) | -Datasource-managed and recording rules are refused with a specific error. A name matching multiple rules errors listing every candidate with the copyable `Folder/Group/Title` and its `uid:` form. A no-match errors with case-insensitive substring suggestions and points at `list`. Duplicate names that resolve to the same uid collapse to one (a note, not an error). +Recording rules are refused with a specific error. Datasource-managed rules never reach resolution at all: the Grafana-managed ruler endpoint this tool queries does not return them. A no-match errors with case-insensitive substring suggestions and points at `list`, and states that datasource-managed rules cannot be observed and are not supported. A name matching multiple rules errors listing every candidate with the copyable `Folder/Group/Title` and its `uid:` form. Duplicate names that resolve to the same uid collapse to one (a note, not an error). ## Selecting alerts by labels @@ -118,7 +118,7 @@ grafana-alertcheck check --to "$finished_at" --include-labels team=bcm --exclude `--include-labels` takes comma-separated exact-match `key=value` pairs; a rule must carry **all** of them. `--exclude-labels` is optional and drops any rule carrying **one** of its pairs. A rule that does not carry the label is never dropped, only never included. Values cannot contain commas; `key=` matches only rules that carry the label with an empty value. -The label flags cannot be combined with `--alerts` or `--folder`, and they are refused with `--in` — the recorded log names its own alert set. A selection that matches no rules, whose matches are all excluded, or that matches a datasource-managed or recording rule exits `2`: an empty watch set must never pass. +The label flags cannot be combined with `--alerts` or `--folder`, and they are refused with `--in` — the recorded log names its own alert set. A selection that matches no rules, whose matches are all excluded, or that matches a recording rule exits `2`: an empty watch set must never pass. ## Output and exit codes diff --git a/grafana-alertcheck/internal/gate/resolve.go b/grafana-alertcheck/internal/gate/resolve.go index 329644f95..165f25e3c 100644 --- a/grafana-alertcheck/internal/gate/resolve.go +++ b/grafana-alertcheck/internal/gate/resolve.go @@ -173,10 +173,12 @@ func refuseUnsupportedKind(name string, d Definition) (Definition, error) { } } -// noMatchError reports a no-match with the count of rules the gate could see -// and case-insensitive substring matches as suggestions. +// noMatchError reports a no-match with the count of grafana-managed rules and +// case-insensitive substring suggestions. The trailing disclaimer covers rules +// the ruler response omits entirely, namely datasource-managed ones. func noMatchError(defs []Definition, name, wantTitle string) error { - msg := fmt.Sprintf("no rule matched %q (%d rules available; run 'grafana-alertcheck list' to see titles)", name, len(defs)) + msg := fmt.Sprintf("no rule matched %q (%d grafana-managed rules available; run 'grafana-alertcheck list' to see titles)", + name, len(defs)) needle := strings.ToLower(wantTitle) var subs []string @@ -189,6 +191,7 @@ func noMatchError(defs []Definition, name, wantTitle string) error { sort.Strings(subs) msg += fmt.Sprintf("; did you mean: %s", strings.Join(subs, ", ")) } + msg += "; datasource-managed alert rules cannot be observed and are not supported" return fmt.Errorf("%s", msg) } diff --git a/grafana-alertcheck/internal/gate/resolve_test.go b/grafana-alertcheck/internal/gate/resolve_test.go index 33214516e..bb5088a06 100644 --- a/grafana-alertcheck/internal/gate/resolve_test.go +++ b/grafana-alertcheck/internal/gate/resolve_test.go @@ -61,6 +61,7 @@ func TestResolve_NoMatch(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "no rule matched") require.Contains(t, err.Error(), "list") + require.Contains(t, err.Error(), "datasource-managed alert rules cannot be observed") } func TestResolve_NoMatchSubstringSuggestion(t *testing.T) { @@ -123,7 +124,7 @@ func TestResolve_UnsupportedKindsExcludedFromNoMatchSurfaces(t *testing.T) { _, _, err = Resolve(combined, []string{"Example"}, "") require.Error(t, err, "want a no-match error for a name matching no title exactly") - wantCount := fmt.Sprintf("(%d rules available", len(supported)) + wantCount := fmt.Sprintf("(%d grafana-managed rules available", len(supported)) require.Contains(t, err.Error(), wantCount) require.NotContains(t, err.Error(), "ExampleTargetDown") require.NotContains(t, err.Error(), "example:recorded_metric:rate5m")