mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-05 19:47:37 +09:00
Alerta refuses an alert whose severity is not in its alarm model, so a rule with such a label is dropped every time it fires and the alert never reaches anyone. Seven piraeus-datastore rules used warn and the keda at-max rule used info, and nothing in the tree caught either. The contract collects every PrometheusRule and VMRule document under packages, in alerts files and in chart templates, and fails on any alerting rule whose labels.severity is missing or not in the accepted list. Helm actions are stripped before parsing, so a templated rule is still checked, and a file that cannot be parsed fails the run instead of being skipped. Values are compared as whole words. none stays out of the list because the Alertmanager config routes it to a blackhole receiver. Vendored charts under packages/**/charts/** are out of scope because they are re-vendored rather than edited. Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com> Assisted-by: LLM
230 lines
7.5 KiB
Bash
230 lines
7.5 KiB
Bash
#!/usr/bin/env bats
|
|
|
|
# Alerta's default alarm model, the set the webhook accepts. `none` is left out
|
|
# on purpose: the Alertmanager config routes severity="none" to a blackhole
|
|
# receiver, so such an alert is never delivered, which is the outcome this
|
|
# contract exists to catch. Rule files under packages/**/charts/** are out of
|
|
# scope: they are re-vendored by `make update`, so a bad severity there is
|
|
# fixed upstream or in patches/, not by editing the label.
|
|
ALERT_SEVERITY_ALLOWED=" security critical major minor warning indeterminate informational normal ok cleared debug trace unknown "
|
|
|
|
helm_actions_stripped() {
|
|
awk '
|
|
function strip_comments(s, pre, rest) {
|
|
while (1) {
|
|
if (incomment) {
|
|
if (match(s, /\*\/[ \t]*-?[}][}]/)) { s = substr(s, RSTART + RLENGTH); incomment = 0 } else { return "" }
|
|
}
|
|
if (match(s, /[{][{]-?[ \t]*\/\*/)) {
|
|
pre = substr(s, 1, RSTART - 1); rest = substr(s, RSTART + RLENGTH)
|
|
if (match(rest, /\*\/[ \t]*-?[}][}]/)) { s = pre substr(rest, RSTART + RLENGTH) } else { incomment = 1; return pre }
|
|
} else { return s }
|
|
}
|
|
}
|
|
{
|
|
line = strip_comments($0)
|
|
if (incomment && line ~ /^[ \t]*$/) { next }
|
|
if (line ~ /^[ \t]*[{][{].*[}][}][ \t]*$/) { next }
|
|
gsub(/[{][{][^}]*[}][}]/, "TEMPLATED", line)
|
|
print line
|
|
}
|
|
' "$1"
|
|
}
|
|
|
|
alert_severity_rows() {
|
|
severity_root=${ALERT_SEVERITY_ROOT:-"$(dirname "${BATS_TEST_FILENAME:-$0}")/.."}
|
|
severity_files=$(mktemp) || return 1
|
|
severity_rows=$(mktemp) || {
|
|
rm -f "$severity_files"
|
|
return 1
|
|
}
|
|
severity_stripped=$(mktemp) || {
|
|
rm -f "$severity_files" "$severity_rows"
|
|
return 1
|
|
}
|
|
|
|
if ! (
|
|
cd "$severity_root" &&
|
|
git grep -Il -E '^[[:space:]]*kind:[[:space:]]*(PrometheusRule|VMRule)[[:space:]]*$' -- \
|
|
'packages/**/*.yaml' \
|
|
'packages/**/*.yml' \
|
|
':(exclude)packages/**/charts/**'
|
|
) > "$severity_files"; then
|
|
rm -f "$severity_files" "$severity_rows" "$severity_stripped"
|
|
return 1
|
|
fi
|
|
|
|
if ! : > "$severity_rows"; then
|
|
rm -f "$severity_files" "$severity_rows" "$severity_stripped"
|
|
return 1
|
|
fi
|
|
while IFS= read -r file; do
|
|
if ! (cd "$severity_root" && helm_actions_stripped "$file" > "$severity_stripped" && FILE="$file" yq 'select(.kind == "PrometheusRule" or .kind == "VMRule") | .spec.groups[]? | .rules[]? | select(has("alert")) | [strenv(FILE), .alert, ((.labels.severity // "<missing>") | tostring)] | @tsv' "$severity_stripped") >> "$severity_rows"; then
|
|
echo "$file: failed to parse alert rules" >&2
|
|
rm -f "$severity_files" "$severity_rows" "$severity_stripped"
|
|
return 1
|
|
fi
|
|
done < "$severity_files"
|
|
|
|
if ! sed '/^[[:space:]]*$/d' "$severity_rows"; then
|
|
rm -f "$severity_files" "$severity_rows" "$severity_stripped"
|
|
return 1
|
|
fi
|
|
rm -f "$severity_files" "$severity_rows" "$severity_stripped"
|
|
}
|
|
|
|
alert_severity_contract() {
|
|
rows=$(mktemp) || return 1
|
|
if ! alert_severity_rows > "$rows"; then
|
|
rm -f "$rows"
|
|
return 1
|
|
fi
|
|
bad=0
|
|
tab=$(printf '\t')
|
|
while IFS="$tab" read -r file alert severity; do
|
|
accepted=0
|
|
for allowed in $ALERT_SEVERITY_ALLOWED; do
|
|
if [ "$allowed" = "$severity" ]; then
|
|
accepted=1
|
|
break
|
|
fi
|
|
done
|
|
if [ "$accepted" -eq 0 ]; then
|
|
echo "$file: $alert -> '$severity'" >&2
|
|
bad=$((bad + 1))
|
|
fi
|
|
done < "$rows"
|
|
rm -f "$rows"
|
|
[ "$bad" -eq 0 ] || return 1
|
|
}
|
|
|
|
@test "alert rules are discovered" {
|
|
rows=$(mktemp) || return 1
|
|
if ! alert_severity_rows > "$rows"; then
|
|
rm -f "$rows"
|
|
return 1
|
|
fi
|
|
count=$(wc -l < "$rows" | tr -d ' ')
|
|
rm -f "$rows"
|
|
[ "$count" -gt 0 ]
|
|
}
|
|
|
|
@test "every alert rule carries a severity the alert sink accepts" {
|
|
alert_severity_contract
|
|
}
|
|
|
|
@test "a parser failure cannot leave the contract green" {
|
|
fixture=$(mktemp -d)
|
|
mkdir -p "$fixture/packages/system/example/alerts"
|
|
printf '%s\n' \
|
|
'apiVersion: operator.victoriametrics.com/v1beta1' \
|
|
'kind: VMRule' \
|
|
'spec:' \
|
|
' groups:' \
|
|
' - name: valid' \
|
|
' rules:' \
|
|
' - alert: ValidAlert' \
|
|
' labels:' \
|
|
' severity: warning' \
|
|
> "$fixture/packages/system/example/alerts/00-valid.yaml"
|
|
printf '%s\n' \
|
|
'apiVersion: operator.victoriametrics.com/v1beta1' \
|
|
'kind: VMRule' \
|
|
'spec: [' \
|
|
> "$fixture/packages/system/example/alerts/99-malformed.yaml"
|
|
git -C "$fixture" init -q
|
|
git -C "$fixture" add packages/system/example/alerts/00-valid.yaml packages/system/example/alerts/99-malformed.yaml
|
|
|
|
parser_succeeded=0
|
|
ALERT_SEVERITY_ROOT="$fixture" alert_severity_rows >/dev/null 2>&1 || parser_succeeded=$?
|
|
rm -rf "$fixture"
|
|
[ "$parser_succeeded" -ne 0 ]
|
|
}
|
|
|
|
@test "template rule sources cannot bypass the severity contract" {
|
|
fixture=$(mktemp -d)
|
|
mkdir -p "$fixture/packages/extra/example/templates"
|
|
printf '%s\n' \
|
|
'apiVersion: monitoring.coreos.com/v1' \
|
|
'kind: PrometheusRule' \
|
|
'spec:' \
|
|
' groups:' \
|
|
' - name: template' \
|
|
' rules:' \
|
|
' - alert: InvalidTemplateSeverity' \
|
|
' labels:' \
|
|
' severity: warn' \
|
|
> "$fixture/packages/extra/example/templates/prometheus-rules.yaml"
|
|
git -C "$fixture" init -q
|
|
git -C "$fixture" add packages/extra/example/templates/prometheus-rules.yaml
|
|
|
|
contract_succeeded=0
|
|
ALERT_SEVERITY_ROOT="$fixture" alert_severity_contract >/dev/null 2>&1 || contract_succeeded=$?
|
|
rm -rf "$fixture"
|
|
[ "$contract_succeeded" -ne 0 ]
|
|
}
|
|
|
|
@test "helm template actions do not hide a rule from the contract" {
|
|
fixture=$(mktemp -d)
|
|
mkdir -p "$fixture/packages/system/example/templates"
|
|
printf '%s\n' \
|
|
'{{- if .Values.alerts.enabled }}' \
|
|
'{{- /*' \
|
|
'Rules gated on a value, behind a comment that spans lines.' \
|
|
'*/}}' \
|
|
'apiVersion: monitoring.coreos.com/v1' \
|
|
'kind: PrometheusRule' \
|
|
'metadata:' \
|
|
' name: example' \
|
|
' namespace: {{ .Release.Namespace }}' \
|
|
'spec:' \
|
|
' groups:' \
|
|
' - name: example' \
|
|
' rules:' \
|
|
' {{- /* one-line comment */}}' \
|
|
' - alert: TemplatedRuleWithBadSeverity' \
|
|
' expr: up == 0' \
|
|
' labels:' \
|
|
' severity: warn' \
|
|
' annotations:' \
|
|
' description: {{ "{{ $labels.instance }}" }} is down' \
|
|
'{{- end }}' \
|
|
> "$fixture/packages/system/example/templates/alerts.yaml"
|
|
git -C "$fixture" init -q
|
|
git -C "$fixture" add packages/system/example/templates/alerts.yaml
|
|
|
|
rows=$(mktemp)
|
|
parser_succeeded=0
|
|
ALERT_SEVERITY_ROOT="$fixture" alert_severity_rows > "$rows" 2>/dev/null || parser_succeeded=$?
|
|
found=$(grep -c 'TemplatedRuleWithBadSeverity' "$rows" || true)
|
|
contract_succeeded=0
|
|
ALERT_SEVERITY_ROOT="$fixture" alert_severity_contract >/dev/null 2>&1 || contract_succeeded=$?
|
|
rm -rf "$fixture" "$rows"
|
|
[ "$parser_succeeded" -eq 0 ]
|
|
[ "$found" -eq 1 ]
|
|
[ "$contract_succeeded" -ne 0 ]
|
|
}
|
|
|
|
@test "two accepted words in one severity are rejected" {
|
|
fixture=$(mktemp -d)
|
|
mkdir -p "$fixture/packages/system/example/alerts"
|
|
printf '%s\n' \
|
|
'apiVersion: operator.victoriametrics.com/v1beta1' \
|
|
'kind: VMRule' \
|
|
'spec:' \
|
|
' groups:' \
|
|
' - name: joined' \
|
|
' rules:' \
|
|
' - alert: JoinedSeverity' \
|
|
' labels:' \
|
|
' severity: critical major' \
|
|
> "$fixture/packages/system/example/alerts/00-joined.yaml"
|
|
git -C "$fixture" init -q
|
|
git -C "$fixture" add packages/system/example/alerts/00-joined.yaml
|
|
|
|
contract_succeeded=0
|
|
ALERT_SEVERITY_ROOT="$fixture" alert_severity_contract >/dev/null 2>&1 || contract_succeeded=$?
|
|
rm -rf "$fixture"
|
|
[ "$contract_succeeded" -ne 0 ]
|
|
}
|