alertmanager.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import { lastValueFrom } from 'rxjs';
  2. import { urlUtil } from '@grafana/data';
  3. import { getBackendSrv } from '@grafana/runtime';
  4. import {
  5. AlertmanagerAlert,
  6. AlertManagerCortexConfig,
  7. AlertmanagerGroup,
  8. AlertmanagerStatus,
  9. ExternalAlertmanagersResponse,
  10. Matcher,
  11. Receiver,
  12. Silence,
  13. SilenceCreatePayload,
  14. TestReceiversAlert,
  15. TestReceiversPayload,
  16. TestReceiversResult,
  17. ExternalAlertmanagerConfig,
  18. } from 'app/plugins/datasource/alertmanager/types';
  19. import { isFetchError } from '../utils/alertmanager';
  20. import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
  21. // "grafana" for grafana-managed, otherwise a datasource name
  22. export async function fetchAlertManagerConfig(alertManagerSourceName: string): Promise<AlertManagerCortexConfig> {
  23. try {
  24. const result = await lastValueFrom(
  25. getBackendSrv().fetch<AlertManagerCortexConfig>({
  26. url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/config/api/v1/alerts`,
  27. showErrorAlert: false,
  28. showSuccessAlert: false,
  29. })
  30. );
  31. return {
  32. template_files: result.data.template_files ?? {},
  33. alertmanager_config: result.data.alertmanager_config ?? {},
  34. };
  35. } catch (e) {
  36. // if no config has been uploaded to grafana, it returns error instead of latest config
  37. if (
  38. alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME &&
  39. e.data?.message?.includes('could not find an Alertmanager configuration')
  40. ) {
  41. return {
  42. template_files: {},
  43. alertmanager_config: {},
  44. };
  45. }
  46. throw e;
  47. }
  48. }
  49. export async function updateAlertManagerConfig(
  50. alertManagerSourceName: string,
  51. config: AlertManagerCortexConfig
  52. ): Promise<void> {
  53. await lastValueFrom(
  54. getBackendSrv().fetch({
  55. method: 'POST',
  56. url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/config/api/v1/alerts`,
  57. data: config,
  58. showErrorAlert: false,
  59. showSuccessAlert: false,
  60. })
  61. );
  62. }
  63. export async function deleteAlertManagerConfig(alertManagerSourceName: string): Promise<void> {
  64. await lastValueFrom(
  65. getBackendSrv().fetch({
  66. method: 'DELETE',
  67. url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/config/api/v1/alerts`,
  68. showErrorAlert: false,
  69. showSuccessAlert: false,
  70. })
  71. );
  72. }
  73. export async function fetchSilences(alertManagerSourceName: string): Promise<Silence[]> {
  74. const result = await lastValueFrom(
  75. getBackendSrv().fetch<Silence[]>({
  76. url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/api/v2/silences`,
  77. showErrorAlert: false,
  78. showSuccessAlert: false,
  79. })
  80. );
  81. return result.data;
  82. }
  83. // returns the new silence ID. Even in the case of an update, a new silence is created and the previous one expired.
  84. export async function createOrUpdateSilence(
  85. alertmanagerSourceName: string,
  86. payload: SilenceCreatePayload
  87. ): Promise<Silence> {
  88. const result = await lastValueFrom(
  89. getBackendSrv().fetch<Silence>({
  90. url: `/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/silences`,
  91. data: payload,
  92. showErrorAlert: false,
  93. showSuccessAlert: false,
  94. method: 'POST',
  95. })
  96. );
  97. return result.data;
  98. }
  99. export async function expireSilence(alertmanagerSourceName: string, silenceID: string): Promise<void> {
  100. await getBackendSrv().delete(
  101. `/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/silence/${encodeURIComponent(silenceID)}`
  102. );
  103. }
  104. export async function fetchAlerts(
  105. alertmanagerSourceName: string,
  106. matchers?: Matcher[],
  107. silenced = true,
  108. active = true,
  109. inhibited = true
  110. ): Promise<AlertmanagerAlert[]> {
  111. const filters =
  112. urlUtil.toUrlParams({ silenced, active, inhibited }) +
  113. matchers
  114. ?.map(
  115. (matcher) =>
  116. `filter=${encodeURIComponent(
  117. `${escapeQuotes(matcher.name)}=${matcher.isRegex ? '~' : ''}"${escapeQuotes(matcher.value)}"`
  118. )}`
  119. )
  120. .join('&') || '';
  121. const result = await lastValueFrom(
  122. getBackendSrv().fetch<AlertmanagerAlert[]>({
  123. url:
  124. `/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/alerts` +
  125. (filters ? '?' + filters : ''),
  126. showErrorAlert: false,
  127. showSuccessAlert: false,
  128. })
  129. );
  130. return result.data;
  131. }
  132. export async function fetchAlertGroups(alertmanagerSourceName: string): Promise<AlertmanagerGroup[]> {
  133. const result = await lastValueFrom(
  134. getBackendSrv().fetch<AlertmanagerGroup[]>({
  135. url: `/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/alerts/groups`,
  136. showErrorAlert: false,
  137. showSuccessAlert: false,
  138. })
  139. );
  140. return result.data;
  141. }
  142. export async function fetchStatus(alertManagerSourceName: string): Promise<AlertmanagerStatus> {
  143. const result = await lastValueFrom(
  144. getBackendSrv().fetch<AlertmanagerStatus>({
  145. url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/api/v2/status`,
  146. showErrorAlert: false,
  147. showSuccessAlert: false,
  148. })
  149. );
  150. return result.data;
  151. }
  152. export async function testReceivers(
  153. alertManagerSourceName: string,
  154. receivers: Receiver[],
  155. alert?: TestReceiversAlert
  156. ): Promise<void> {
  157. const data: TestReceiversPayload = {
  158. receivers,
  159. alert,
  160. };
  161. try {
  162. const result = await lastValueFrom(
  163. getBackendSrv().fetch<TestReceiversResult>({
  164. method: 'POST',
  165. data,
  166. url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/config/api/v1/receivers/test`,
  167. showErrorAlert: false,
  168. showSuccessAlert: false,
  169. })
  170. );
  171. if (receiversResponseContainsErrors(result.data)) {
  172. throw new Error(getReceiverResultError(result.data));
  173. }
  174. } catch (error) {
  175. if (isFetchError(error) && isTestReceiversResult(error.data) && receiversResponseContainsErrors(error.data)) {
  176. throw new Error(getReceiverResultError(error.data));
  177. }
  178. throw error;
  179. }
  180. }
  181. function receiversResponseContainsErrors(result: TestReceiversResult) {
  182. return result.receivers.some((receiver) =>
  183. receiver.grafana_managed_receiver_configs.some((config) => config.status === 'failed')
  184. );
  185. }
  186. function isTestReceiversResult(data: any): data is TestReceiversResult {
  187. const receivers = data?.receivers;
  188. if (Array.isArray(receivers)) {
  189. return receivers.every(
  190. (receiver: any) => typeof receiver.name === 'string' && Array.isArray(receiver.grafana_managed_receiver_configs)
  191. );
  192. }
  193. return false;
  194. }
  195. function getReceiverResultError(receiversResult: TestReceiversResult) {
  196. return receiversResult.receivers
  197. .flatMap((receiver) =>
  198. receiver.grafana_managed_receiver_configs
  199. .filter((receiver) => receiver.status === 'failed')
  200. .map((receiver) => receiver.error ?? 'Unknown error.')
  201. )
  202. .join('; ');
  203. }
  204. export async function addAlertManagers(alertManagerConfig: ExternalAlertmanagerConfig): Promise<void> {
  205. await lastValueFrom(
  206. getBackendSrv().fetch({
  207. method: 'POST',
  208. data: alertManagerConfig,
  209. url: '/api/v1/ngalert/admin_config',
  210. showErrorAlert: false,
  211. showSuccessAlert: false,
  212. })
  213. ).then(() => {
  214. fetchExternalAlertmanagerConfig();
  215. });
  216. }
  217. export async function fetchExternalAlertmanagers(): Promise<ExternalAlertmanagersResponse> {
  218. const result = await lastValueFrom(
  219. getBackendSrv().fetch<ExternalAlertmanagersResponse>({
  220. method: 'GET',
  221. url: '/api/v1/ngalert/alertmanagers',
  222. })
  223. );
  224. return result.data;
  225. }
  226. export async function fetchExternalAlertmanagerConfig(): Promise<ExternalAlertmanagerConfig> {
  227. const result = await lastValueFrom(
  228. getBackendSrv().fetch<ExternalAlertmanagerConfig>({
  229. method: 'GET',
  230. url: '/api/v1/ngalert/admin_config',
  231. showErrorAlert: false,
  232. })
  233. );
  234. return result.data;
  235. }
  236. function escapeQuotes(value: string): string {
  237. return value.replace(/"/g, '\\"');
  238. }