@testcompany888/backstage-plugin-hetzner
v0.1.1
Published
Backstage frontend plugin for Hetzner Cloud integration.
Readme
Backstage Hetzner Plugin
A plugin for Backstage that integrates with Hetzner Cloud. This plugin allows you to:
- View an overview of your Hetzner Cloud infrastructure directly in Backstage
- Inspect details of Hetzner resources (servers, volumes, IPs, etc.) via the Index Page
- Navigate and filter Hetzner resources as part of your Backstage catalog experience
Table of Contents
Features
The Hetzner Cloud to Backstage plugin needs to give a comprehensive insight into the Hetzner cloud platform. There are two views that provide information:
Index Page: Gives an overview of the Hetzner Cloud Platform in general.

Resource Card: Gives details about which resources are used and what runs where. This view is available in a tab in each component that uses Hetzner's resources.

Installation
Frontend Plugin
# From the Backstage root directory
yarn --cwd packages/app add @gluo-nv/backstage-plugin-hetznerBackend Plugin
This frontend plugin requires the corresponding backend plugin to be installed. Please follow the instructions in the backend plugin directory.
Catalog Backend Module Plugin
This frontend plugin requires the corresponding catalog backend module plugin to be installed. Please follow the instructions in the catalog backend module plugin directory.
Configuration
Frontend Configuration
Add the following to the app-config.yaml to configure the frontend plugin:
app:
hetzner:
project:
title: <Hetzner project title>
owner: <Hetzner project owner>
lifecycle: <Hetzner project lifecycle>- Add the plugin to the Backstage application by modifying the
packages/app/src/App.tsx:
import { HetznerPage } from '@gluo-nv/backstage-plugin-hetzner';Add the Hetzner Cloud page to the sidebar
const routes = (
<FlatRoutes>
{/* ...other routes*/}
<Route path="/hetzner" element={<HetznerPage />} />
</FlatRoutes>
);- ``packages/app/src/components/Root/Root.tsx`
Add the icon import:
import Cloud from '@material-ui/icons/Cloud';- Then add this line just before the
</SidebarGroup>line:
export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarPage>
<Sidebar>
{/*...*/}
<SidebarGroup label="Menu" icon={<MenuIcon />}>
{/* Global nav, not org-specific */}
{/*...other sidebar items*/}
<SidebarItem icon={Cloud} to="hetzner" text="Hetzner Cloud" />
{/* End global nav */}
<SidebarDivider />
{/*...*/}
</SidebarGroup>
{/*...*/}
</Sidebar>
{children}
</SidebarPage>
);- Add the Hetzner Resources Card to the entity overview page
Add a new file utils.tsx in packages/app/src/components/catalog:
import { Entity } from '@backstage/catalog-model';
export const isHetznerResource = (entity: Entity): boolean => {
const hetznerData = entity.metadata.annotations?.['hetzner.com/data'];
try {
return hetznerData !== undefined && JSON.parse(hetznerData) !== null;
} catch (e) {
return false;
}
};Add the Resources Card in packages/app/src/components/catalog/EntityPage.tsx
import { EntityHetznerContent } from '@gluo-nv/backstage-plugin-hetzner';
import { isHetznerResource } from './utils';
// Add to the overview content:
const overviewContent = (
<Grid container spacing={3} alignItems="stretch">
{entityWarningContent}
<Grid item md={6}>
<EntityAboutCard variant="gridItem" />
</Grid>
<Grid item md={6} xs={12}>
<EntityCatalogGraphCard variant="gridItem" height={400} />
</Grid>
{/* Hetzner Cloud Plugin */}
<EntitySwitch>
<EntitySwitch.Case
if={entity => isKind('resource')(entity) && isHetznerResource(entity)}
>
<Grid item md={6}>
<EntityHetznerContent />
</Grid>
</EntitySwitch.Case>
</EntitySwitch>
{/* Hetzner Cloud Plugin */}
{/*...*/}
</Grid>
);Development
Getting Started
- Clone the repository
- Install dependencies:
yarn install- Run the plugin in isolation
yarn startRunning Tests
Run all tests:
yarn testRun tests with coverage:
yarn test:coverageBuilding
yarn buildCustom test environment
To set up a custom development environment for the Hetzner Cloud plugin, follow these steps:
- Create or update the
dev/index.tsxfile with the following content:
import { createDevApp } from '@backstage/dev-utils';
import {
hetznerCloudPlugin,
HetznerCloudPage,
ResourcesCardPage,
} from '../src/plugin';
// dev
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { CustomHetznerAboutCard } from '../src/components/custom/HetznerEntityAboutCard';
// dev
const mockEntity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'mock-entity',
annotations: {
'hetzner.com/data': JSON.stringify({
id: 12345,
name: 'vm-gluo-rke2-runner-1',
status: 'running',
created: '2024-11-25T10:04:10+00:00',
public_net: {
ipv4: {
ip: '192.168.0.1',
dns_ptr: 'example.com',
},
},
datacenter: {
name: 'nbg1-dc3',
description: 'Nuremberg 1 virtual DC 3',
location: {
city: 'Nuremberg',
country: 'DE',
},
},
server_type: {
name: 'cx32',
description: 'CX32',
cores: 4,
memory: 8,
disk: 80,
},
image: {
name: 'debian-12',
description: 'Debian 12',
os_flavor: 'debian',
os_version: '12',
},
outgoing_traffic: 12345,
ingoing_traffic: 67890,
included_traffic: 100000,
resource_type: 'virtual_machine',
}),
},
},
};
createDevApp()
.registerPlugin(hetznerCloudPlugin)
.addPage({
element: <HetznerCloudPage />,
title: 'Root Page',
path: '/hetzner-cloud',
})
// dev
.addPage({
title: 'Resources Card',
element: (
<EntityProvider entity={mockEntity}>
<ResourcesCard />
</EntityProvider>
),
path: '/resources-card',
})
.render();This file is the entry point for running the plugin in a standalone development environment. It allows you to test the plugin without integrating it into a full Backstage app.
- Ensure the
src/routes.tsfile contains the following:
import { createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
id: 'hetzner-cloud',
});
// dev
export const resourcesCardRouteRef = createRouteRef({
id: 'resources-card',
});This file defines the route references for the plugin. Route references are used to link components to specific paths in Backstage.
- Ensure the
src/plugin.tsfile contains the following:
import {
createPlugin,
createRoutableExtension,
} from '@backstage/core-plugin-api';
import { resourcesCardRouteRef, rootRouteRef } from './routes';
export const hetznerCloudPlugin = createPlugin({
id: 'hetzner-cloud',
routes: {
root: rootRouteRef,
resourcesCard: resourcesCardRouteCard, //dev
},
});
// prod
// export const EntityHetznerCloudContent = hetznerCloudPlugin.provide(
// createComponentExtension({
// component: {
// lazy: () => import ('./components/entity/ResourcesCard').then(m => m.ResourcesCard),
// }
// })
// );
export const HetznerCloudPage = hetznerCloudPlugin.provide(
createRoutableExtension({
name: 'HetznerCloudPage',
component: () =>
import('./components/IndexComponent').then(m => m.IndexComponent),
mountPoint: rootRouteRef,
}),
);
// dev
export const ResourcesCardPage = hetznerCloudPlugin.provide(
createRoutableExtension({
name: 'ResourcesCardPage',
component: () =>
import('./components/entity/ResourcesCard').then(m => m.ResourcesCard),
mountPoint: resourcesCardRouteRef,
}),
);This file defines the main plugin and its extensions. It connects the plugin's routes to their corresponding components.
- Ensure the
src/index.tsfile contains the following:
export {
hetznerCloudPlugin,
HetznerCloudPage,
ResourcesCardPage, //dev
} from './plugin';This file exports the plugin and its extensions so they can be imported and used in other parts of the application.
Contributing
We welcome contributions to improve this plugin! If you’d like to contribute, please follow these steps:
- Fork the repository.
- Create a new branch for your feature or bug fix.
- Submit a pull request with a clear description of your changes.
For major changes, please open an issue first to discuss your ideas.
License
This plugin is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
Attribution
This plugin was created by Gluo NV.
Any use or distribution must include proper attribution to the original author.
