End of life logo

Backstage End of Life Plugin

Created by dweber019

End of life means a version has reached the end of support from its maintainer. Security patches stop. New bugs stay unfixed. For engineers who run many services, this creates risk. You need a clear view of what will expire and when.

The End of life plugin adds that view to your Backstage catalog. It shows the lifecycle of the tech behind each entity. Data comes from endoflife.date. You can point it at your own source if you track lifecycles internally. The plugin surfaces supported versions, important dates, and a simple status so teams can see what needs attention. It lives with the rest of your service data, so the context is always there.

Common uses include upgrade planning and risk reviews. You can spot aging frameworks, runtimes, and databases across your catalog. Service owners can plan migrations before support ends. Platform teams can highlight problem areas to leadership with less manual work. API producers can make version lifecycles visible to consumers. The plugin helps keep the catalog trustworthy by making stale versions hard to ignore.

Data quality matters. If a product entry is wrong or missing in the source, it may show up that way in the card. Many teams fix this by contributing updates back to the community source or by providing an internal feed.

Installation Instructions

These instructions apply to self-hosted Backstage only. To use this plugin on Roadie, visit the docs.

Install the frontend package

Run this from the Backstage root

Copy
yarn --cwd packages/app add @dweber019/backstage-plugin-endoflife

Add the card to the Entity page

Import the card and the guard. Then place the card in your Entity page so users can see it.

Copy
// packages/app/src/components/catalog/EntityPage.tsx

import React from 'react';
import Grid from '@material-ui/core/Grid';
import { EntityLayout, EntitySwitch } from '@backstage/plugin-catalog';
import {
  EntityEndOfLifeCard,
  isEndOfLifeAvailable,
} from '@dweber019/backstage-plugin-endoflife';

// keep your existing imports and code above

const overviewContent = (
  <Grid container spacing={3} alignItems="stretch">
    {/* keep your existing content */}
    <EntitySwitch>
      <EntitySwitch.Case if={isEndOfLifeAvailable}>
        <Grid item md={6}>
          <EntityEndOfLifeCard />
        </Grid>
      </EntitySwitch.Case>
    </EntitySwitch>
  </Grid>
);

// if you have a resource page layout you can also add the card there

const resourcePage = (
  <EntityLayout.Route path="/" title="Overview">
    <Grid container spacing={3} alignItems="stretch">
      <EntitySwitch>
        <EntitySwitch.Case if={isEndOfLifeAvailable}>
          <Grid item md={6}>
            <EntityEndOfLifeCard variant="gridItem" />
          </Grid>
        </EntitySwitch.Case>
      </EntitySwitch>
    </Grid>
  </EntityLayout.Route>
);

// keep your existing exports

Add entity annotations

Use the product name from the endoflife site path. You can set a single product or several. You can also pin a version cycle with the at symbol.

Copy
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: eol-angular
  annotations:
    endoflife.date/products: angular
Copy
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: eol-angular-nginx-app
  annotations:
    endoflife.date/products: angular,nginx
Copy
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: eol-angular-nignx-version-app
  annotations:
    endoflife.date/products: angular@17,[email protected]

Load data from a custom url if you do not want to use the public service

Copy
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: angular-app
  annotations:
    endoflife.date/url-location: https://example.com/versions.json

Load data from your source repository

Copy
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: angular-app
  annotations:
    endoflife.date/source-location: /versions.json

The source location path is relative to the repository root
Do not mix these annotations in one entity
The lookup order is products then url location then source location

Configure help text or a custom base url

Set optional app config for the help text shown in the card. You can also point the plugin to a private End of life service.

Copy
# app-config.yaml

endOfLife:
  helpText: |
    # Custom help text

    You can use markdown if you like
  baseUrl: https://endoflife.mycompany.com

Install the backend for source location support

This is only needed if you use the source location annotation. It enables the backend to read the file from your repository through your configured integrations.

Copy
yarn --cwd packages/backend add @dweber019/backstage-plugin-endoflife-backend

New backend system

Register the backend plugin in your backend startup file.

Copy
// packages/backend/src/index.ts

import { createBackend } from '@backstage/backend-defaults';
import { endOfLifePlugin } from '@dweber019/backstage-plugin-endoflife-backend';

const backend = createBackend();

// add your other plugins here

backend.add(endOfLifePlugin());

// start the backend
backend.start();

If your project uses a builder style

Copy
// packages/backend/src/index.ts

import { createBackend } from '@backstage/backend-app-api';
import { endOfLifePlugin } from '@dweber019/backstage-plugin-endoflife-backend';

const backend = createBackend();

// other registrations

backend.add(endOfLifePlugin());

await backend.start();

Legacy backend system

Create the router and mount it under the api path.

Copy
// packages/backend/src/plugins/endoflife.ts

import { PluginEnvironment } from '../types';
import { createRouter } from '@dweber019/backstage-plugin-endoflife-backend';

export default async function createPlugin(env: PluginEnvironment) {
  return await createRouter({
    logger: env.logger,
    config: env.config,
    reader: env.reader,
    discovery: env.discovery,
  });
}

Mount the router in the main backend

Copy
// packages/backend/src/index.ts

import endoflife from './plugins/endoflife';

// inside the main bootstrap where you set up apiRouter

const endoflifeEnv = useHotMemoize(module, () => createEnv('endoflife'));
apiRouter.use('/endoflife', await endoflife(endoflifeEnv));

Notes on usage

  • Use the card anywhere on an entity page where users will see it
  • The source location annotation requires the backend in place
  • The plugin reads from the public endoflife service by default unless you set a custom base url in app config

Changelog

This changelog is produced from commits made to the End of life plugin since a year ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.

Features

  • Enable custom url for data in the End of life plugin #103 3 months ago
  • Add annotation parameters trim to reduce errors when reading annotations #98 4 months ago

Maintenance

  • Update Backstage to 1.41.1 for the plugin and backend #108 2 months ago
  • Update Backstage to 1.41.0 and remove React imports #101 3 months ago
  • Update Backstage to 1.36.1 #92 6 months ago
  • Update Backstage to 1.35.0 #86 8 months ago
  • Update Backstage to 1.34.2 #84 9 months ago
  • Update Backstage dependencies to 1.32.2 #77 11 months ago

Set up Backstage in minutes with Roadie