buildInfo.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { lastValueFrom } from 'rxjs';
  2. import { getBackendSrv } from '@grafana/runtime';
  3. import { PromApplication, PromApiFeatures, PromBuildInfoResponse } from 'app/types/unified-alerting-dto';
  4. import { isFetchError } from '../utils/alertmanager';
  5. import { RULER_NOT_SUPPORTED_MSG } from '../utils/constants';
  6. import { getDataSourceByName } from '../utils/datasource';
  7. import { fetchRules } from './prometheus';
  8. import { fetchTestRulerRulesGroup } from './ruler';
  9. /**
  10. * This function will attempt to detect what type of system we are talking to; this could be
  11. * Prometheus (vanilla) | Cortex | Mimir
  12. *
  13. * Cortex and Mimir allow editing rules via their API, Prometheus does not.
  14. * Prometheus and Mimir expose a `buildinfo` endpoint, Cortex does not.
  15. * Mimir reports which "features" are enabled or available via the buildinfo endpoint, Prometheus does not.
  16. */
  17. export async function discoverDataSourceFeatures(dsSettings: {
  18. url: string;
  19. name: string;
  20. type: 'prometheus' | 'loki';
  21. }): Promise<PromApiFeatures> {
  22. const { url, name, type } = dsSettings;
  23. // The current implementation of Loki's build info endpoint is useless
  24. // because it doesn't provide information about Loki's available features (e.g. Ruler API)
  25. // It's better to skip fetching it for Loki and go the Cortex path (manual discovery)
  26. const buildInfoResponse = type === 'prometheus' ? await fetchPromBuildInfo(url) : undefined;
  27. // check if the component returns buildinfo
  28. const hasBuildInfo = buildInfoResponse !== undefined;
  29. // we are dealing with a Cortex or Loki datasource since the response for buildinfo came up empty
  30. if (!hasBuildInfo) {
  31. // check if we can fetch rules via the prometheus compatible api
  32. const promRulesSupported = await hasPromRulesSupport(name);
  33. if (!promRulesSupported) {
  34. throw new Error(`Unable to fetch alert rules. Is the ${name} data source properly configured?`);
  35. }
  36. // check if the ruler is enabled
  37. const rulerSupported = await hasRulerSupport(name);
  38. return {
  39. application: PromApplication.Lotex,
  40. features: {
  41. rulerApiEnabled: rulerSupported,
  42. },
  43. };
  44. }
  45. // if no features are reported but buildinfo was return we're talking to Prometheus
  46. const { features } = buildInfoResponse.data;
  47. if (!features) {
  48. return {
  49. application: PromApplication.Prometheus,
  50. features: {
  51. rulerApiEnabled: false,
  52. },
  53. };
  54. }
  55. // if we have both features and buildinfo reported we're talking to Mimir
  56. return {
  57. application: PromApplication.Mimir,
  58. features: {
  59. rulerApiEnabled: features?.ruler_config_api === 'true',
  60. },
  61. };
  62. }
  63. /**
  64. * Attempt to fetch buildinfo from our component
  65. */
  66. export async function discoverFeatures(dataSourceName: string): Promise<PromApiFeatures> {
  67. const dsConfig = getDataSourceByName(dataSourceName);
  68. if (!dsConfig) {
  69. throw new Error(`Cannot find data source configuration for ${dataSourceName}`);
  70. }
  71. const { url, name, type } = dsConfig;
  72. if (!url) {
  73. throw new Error(`The data souce url cannot be empty.`);
  74. }
  75. if (type !== 'prometheus' && type !== 'loki') {
  76. throw new Error(`The build info request is not available for ${type}. Only 'prometheus' and 'loki' are supported`);
  77. }
  78. return discoverDataSourceFeatures({ name, url, type });
  79. }
  80. async function fetchPromBuildInfo(url: string): Promise<PromBuildInfoResponse | undefined> {
  81. const response = await lastValueFrom(
  82. getBackendSrv().fetch<PromBuildInfoResponse>({
  83. url: `${url}/api/v1/status/buildinfo`,
  84. showErrorAlert: false,
  85. showSuccessAlert: false,
  86. })
  87. ).catch((e) => {
  88. if ('status' in e && e.status === 404) {
  89. return undefined; // Cortex does not support buildinfo endpoint, we return an empty response
  90. }
  91. throw e;
  92. });
  93. return response?.data;
  94. }
  95. /**
  96. * Check if the component allows us to fetch rules
  97. */
  98. async function hasPromRulesSupport(dataSourceName: string) {
  99. try {
  100. await fetchRules(dataSourceName);
  101. return true;
  102. } catch (e) {
  103. return false;
  104. }
  105. }
  106. /**
  107. * Attempt to check if the ruler API is enabled for Cortex, Prometheus does not support it and Mimir
  108. * reports this via the buildInfo "features"
  109. */
  110. async function hasRulerSupport(dataSourceName: string) {
  111. try {
  112. await fetchTestRulerRulesGroup(dataSourceName);
  113. return true;
  114. } catch (e) {
  115. if (errorIndicatesMissingRulerSupport(e)) {
  116. return false;
  117. }
  118. throw e;
  119. }
  120. }
  121. // there errors indicate that the ruler API might be disabled or not supported for Cortex
  122. function errorIndicatesMissingRulerSupport(error: any) {
  123. return (
  124. (isFetchError(error) &&
  125. (error.data.message?.includes('GetRuleGroup unsupported in rule local store') || // "local" rule storage
  126. error.data.message?.includes('page not found'))) || // ruler api disabled
  127. error.message?.includes('404 from rules config endpoint') || // ruler api disabled
  128. error.data.message?.includes(RULER_NOT_SUPPORTED_MSG) // ruler api not supported
  129. );
  130. }