Skip to content

Commit

Permalink
Merge pull request #1127 from bforbis/dynamic_prometheus
Browse files Browse the repository at this point in the history
Dynamic prometheus
  • Loading branch information
Xantier authored Sep 22, 2023
2 parents 571a302 + a4c2b85 commit 7a55bb7
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/curvy-stingrays-exist.md
Original file line number Diff line number Diff line change
@@ -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
59 changes: 54 additions & 5 deletions plugins/frontend/backstage-plugin-prometheus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Router> {
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.
Expand Down
12 changes: 11 additions & 1 deletion plugins/frontend/backstage-plugin-prometheus/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PrometheusApi>({
id: 'plugin.prometheus.service',
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down

0 comments on commit 7a55bb7

Please sign in to comment.