diff --git a/.changeset/curvy-stingrays-exist.md b/.changeset/curvy-stingrays-exist.md new file mode 100644 index 000000000..f41373a58 --- /dev/null +++ b/.changeset/curvy-stingrays-exist.md @@ -0,0 +1,5 @@ +--- +'@roadiehq/backstage-plugin-prometheus': minor +--- + +Prometheus service name will now be sent to the backstage backend proxy as a request header to support advanced proxying configs diff --git a/plugins/frontend/backstage-plugin-prometheus/README.md b/plugins/frontend/backstage-plugin-prometheus/README.md index 680833c61..eb6c10edd 100644 --- a/plugins/frontend/backstage-plugin-prometheus/README.md +++ b/plugins/frontend/backstage-plugin-prometheus/README.md @@ -4,11 +4,6 @@ Backstage plugin exposing graphs and alerts from Prometheus ![Prometheus Entity Content Page Screenshot](./docs/prom_entity_content.png) -## Features - -- List Pull Requests for your repository, with filtering and search. -- Show basic statistics widget about pull requests for your repository. - ### The plugin provides an entity content page and two additional widgets: 1. Alert table widget @@ -191,6 +186,60 @@ prometheus: proxyPath: /prometheusTeamB/api ``` +## Advanced Dynamic Prometheus Proxying + +If you have a very large amount of prometheus servers, the above statically configured "Multiple Prometheus instances" proxy config may become verbose and difficult to maintain. You can take full control over Backstage's backend proxying behavior for prometheus by writing your own proxy middleware. + +All prometheus requests from the frontend will send the entities `prometheus.io/service-name` annotation in the `x-prometheus-service-name` request header. + +Step 1: Update app-config to use a special path if it can't find the `prometheus.io/service-name` config in the `prometheus.instances` config array +**app-config.yaml** + +```yaml +prometheus: + proxyPath: '/dynamic-prometheus' +``` + +Step 2: Hijack this path by writing your own proxy middleware extension. +**packages/backend/src/plugins/proxy.ts** + +```diff +import { createRouter } from '@backstage/plugin-proxy-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; ++ import { createProxyMiddleware } from 'http-proxy-middleware'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const proxyRouter = await createRouter({ + logger: env.logger, + config: env.config, + discovery: env.discovery, + }); ++ const externalUrl = await env.discovery.getExternalBaseUrl('proxy'); ++ const { pathname: pathPrefix } = new URL(externalUrl); ++ proxyRouter.use( ++ '/dynamic-prometheus', ++ createProxyMiddleware({ ++ logProvider: () => env.logger, ++ logLevel: 'debug', ++ changeOrigin: true, ++ pathRewrite: { ++ [`^${pathPrefix}/dynamic-prometheus/?`]: '/', ++ }, ++ // Some code that does something with the x-prometheus-service-name header. ++ // Here you can just do URL manipulation, or even make requests out to other services ++ // or caches to pull down lookup info. ++ router: async (req) => { ++ const prometheusServiceName = req.headers['x-prometheus-service-name']; ++ return `https://${prometheusServiceName}.company.com`; ++ }, ++ }), ++ ); ++ return proxyRouter; +``` + ## Using callback with `EntityPrometheusAlertCard` You can add callbacks that will be executed when the user clicks on a row in the table of the `EntityPrometheusAlertCard` component. This callback is optional, and the rows become clickable only when the callback is supplied. The callback function is also provided with data of type JSON. It contains the information from the row of the table. diff --git a/plugins/frontend/backstage-plugin-prometheus/src/api/index.ts b/plugins/frontend/backstage-plugin-prometheus/src/api/index.ts index 568c1f6d0..28486b457 100644 --- a/plugins/frontend/backstage-plugin-prometheus/src/api/index.ts +++ b/plugins/frontend/backstage-plugin-prometheus/src/api/index.ts @@ -22,6 +22,7 @@ import { import { DateTime, Duration } from 'luxon'; const DEFAULT_PROXY_PATH = '/prometheus/api'; +const SERVICE_NAME_HEADER = 'x-prometheus-service-name'; export const prometheusApiRef = createApiRef({ id: 'plugin.prometheus.service', @@ -87,6 +88,11 @@ export class PrometheusApi { const start = DateTime.now().minus(Duration.fromObject(range)).toSeconds(); const response = await fetch( `${apiUrl}/query_range?query=${query}&start=${start}&end=${end}&step=${step}`, + { + headers: { + [SERVICE_NAME_HEADER]: serviceName || '', + }, + }, ); if (!response.ok) { throw new Error( @@ -98,7 +104,11 @@ export class PrometheusApi { async getAlerts({ serviceName }: { serviceName?: string }) { const apiUrl = await this.getApiUrl({ serviceName }); - const response = await fetch(`${apiUrl}/rules?type=alert`); + const response = await fetch(`${apiUrl}/rules?type=alert`, { + headers: { + [SERVICE_NAME_HEADER]: serviceName || '', + }, + }); if (!response.ok) { throw new Error(