The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/alerting/unified/api/alertmanager.ts

276 lines
8.2 KiB

import { lastValueFrom } from 'rxjs';
import { isObject, urlUtil } from '@grafana/data';
import { getBackendSrv, isFetchError } from '@grafana/runtime';
import {
AlertmanagerAlert,
AlertManagerCortexConfig,
AlertmanagerGroup,
AlertmanagerStatus,
ExternalAlertmanagerConfig,
ExternalAlertmanagersResponse,
Matcher,
Receiver,
Silence,
SilenceCreatePayload,
TestReceiversAlert,
TestReceiversPayload,
TestReceiversResult,
} from 'app/plugins/datasource/alertmanager/types';
import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
// "grafana" for grafana-managed, otherwise a datasource name
export async function fetchAlertManagerConfig(alertManagerSourceName: string): Promise<AlertManagerCortexConfig> {
try {
const result = await lastValueFrom(
getBackendSrv().fetch<AlertManagerCortexConfig>({
url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/config/api/v1/alerts`,
showErrorAlert: false,
showSuccessAlert: false,
})
);
return {
template_files: result.data.template_files ?? {},
template_file_provenances: result.data.template_file_provenances ?? {},
alertmanager_config: result.data.alertmanager_config ?? {},
last_applied: result.data.last_applied,
id: result.data.id,
};
} catch (e) {
// if no config has been uploaded to grafana, it returns error instead of latest config
if (
alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME &&
isFetchError(e) &&
e.data?.message?.includes('could not find an Alertmanager configuration')
) {
return {
template_files: {},
alertmanager_config: {},
};
}
throw e;
}
}
export async function updateAlertManagerConfig(
alertManagerSourceName: string,
config: AlertManagerCortexConfig
): Promise<void> {
await lastValueFrom(
getBackendSrv().fetch({
method: 'POST',
url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/config/api/v1/alerts`,
data: config,
showErrorAlert: false,
showSuccessAlert: false,
})
);
}
export async function deleteAlertManagerConfig(alertManagerSourceName: string): Promise<void> {
await lastValueFrom(
getBackendSrv().fetch({
method: 'DELETE',
url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/config/api/v1/alerts`,
showErrorAlert: false,
showSuccessAlert: false,
})
);
}
export async function fetchSilences(alertManagerSourceName: string): Promise<Silence[]> {
const result = await lastValueFrom(
getBackendSrv().fetch<Silence[]>({
url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/api/v2/silences`,
showErrorAlert: false,
showSuccessAlert: false,
})
);
return result.data;
}
// returns the new silence ID. Even in the case of an update, a new silence is created and the previous one expired.
export async function createOrUpdateSilence(
alertmanagerSourceName: string,
payload: SilenceCreatePayload
): Promise<Silence> {
const result = await lastValueFrom(
getBackendSrv().fetch<Silence>({
url: `/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/silences`,
data: payload,
showErrorAlert: false,
showSuccessAlert: false,
method: 'POST',
})
);
return result.data;
}
export async function expireSilence(alertmanagerSourceName: string, silenceID: string): Promise<void> {
await getBackendSrv().delete(
`/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/silence/${encodeURIComponent(silenceID)}`
);
}
export async function fetchAlerts(
alertmanagerSourceName: string,
matchers?: Matcher[],
silenced = true,
active = true,
inhibited = true
): Promise<AlertmanagerAlert[]> {
const filters =
urlUtil.toUrlParams({ silenced, active, inhibited }) +
matchers
?.map(
(matcher) =>
`filter=${encodeURIComponent(
`${escapeQuotes(matcher.name)}=${matcher.isRegex ? '~' : ''}"${escapeQuotes(matcher.value)}"`
)}`
)
.join('&') || '';
const result = await lastValueFrom(
getBackendSrv().fetch<AlertmanagerAlert[]>({
url:
`/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/alerts` +
(filters ? '?' + filters : ''),
showErrorAlert: false,
showSuccessAlert: false,
})
);
return result.data;
}
export async function fetchAlertGroups(alertmanagerSourceName: string): Promise<AlertmanagerGroup[]> {
const result = await lastValueFrom(
getBackendSrv().fetch<AlertmanagerGroup[]>({
url: `/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/alerts/groups`,
showErrorAlert: false,
showSuccessAlert: false,
})
);
return result.data;
}
export async function fetchStatus(alertManagerSourceName: string): Promise<AlertmanagerStatus> {
const result = await lastValueFrom(
getBackendSrv().fetch<AlertmanagerStatus>({
url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/api/v2/status`,
showErrorAlert: false,
showSuccessAlert: false,
})
);
return result.data;
}
export async function testReceivers(
alertManagerSourceName: string,
receivers: Receiver[],
alert?: TestReceiversAlert
): Promise<void> {
const data: TestReceiversPayload = {
receivers,
alert,
};
try {
const result = await lastValueFrom(
getBackendSrv().fetch<TestReceiversResult>({
method: 'POST',
data,
url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/config/api/v1/receivers/test`,
showErrorAlert: false,
showSuccessAlert: false,
})
);
if (receiversResponseContainsErrors(result.data)) {
throw new Error(getReceiverResultError(result.data));
}
} catch (error) {
if (isFetchError(error) && isTestReceiversResult(error.data) && receiversResponseContainsErrors(error.data)) {
throw new Error(getReceiverResultError(error.data));
}
throw error;
}
}
function receiversResponseContainsErrors(result: TestReceiversResult) {
return result.receivers.some((receiver) =>
receiver.grafana_managed_receiver_configs.some((config) => config.status === 'failed')
);
}
function isTestReceiversResult(data: unknown): data is TestReceiversResult {
if (isObject(data) && 'receivers' in data && Array.isArray(data.receivers)) {
return data.receivers.every(isSingleTestRecieverResult);
}
return false;
}
function isSingleTestRecieverResult(receiver: unknown): receiver is TestReceiversResult {
return (
isObject(receiver) &&
'name' in receiver &&
typeof receiver.name === 'string' &&
'grafana_managed_receiver_configs' in receiver &&
Array.isArray(receiver.grafana_managed_receiver_configs)
);
}
function getReceiverResultError(receiversResult: TestReceiversResult) {
return receiversResult.receivers
.flatMap((receiver) =>
receiver.grafana_managed_receiver_configs
.filter((receiver) => receiver.status === 'failed')
.map((receiver) => receiver.error ?? 'Unknown error.')
)
.join('; ');
}
export async function addAlertManagers(alertManagerConfig: ExternalAlertmanagerConfig): Promise<void> {
await lastValueFrom(
getBackendSrv().fetch({
method: 'POST',
data: alertManagerConfig,
url: '/api/v1/ngalert/admin_config',
showErrorAlert: false,
showSuccessAlert: false,
})
).then(() => {
fetchExternalAlertmanagerConfig();
});
}
export async function fetchExternalAlertmanagers(): Promise<ExternalAlertmanagersResponse> {
const result = await lastValueFrom(
getBackendSrv().fetch<ExternalAlertmanagersResponse>({
method: 'GET',
url: '/api/v1/ngalert/alertmanagers',
})
);
return result.data;
}
export async function fetchExternalAlertmanagerConfig(): Promise<ExternalAlertmanagerConfig> {
const result = await lastValueFrom(
getBackendSrv().fetch<ExternalAlertmanagerConfig>({
method: 'GET',
url: '/api/v1/ngalert/admin_config',
showErrorAlert: false,
})
);
return result.data;
}
function escapeQuotes(value: string): string {
return value.replace(/"/g, '\\"');
}