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