Merge pull request #21426 from backstage/rugvip/backend-plugin-docs
docs/backend-system: update plugin migration docs
This commit is contained in:
@@ -73,30 +73,99 @@ export const kubernetesPlugin = createBackendPlugin({
|
||||
});
|
||||
```
|
||||
|
||||
Done! Users of this plugin are now able to import the `kubernetesPlugin` and register it in their backend using
|
||||
Lastly, make sure you re-export the plugin instance as the default export of your package in `src/index.ts`:
|
||||
|
||||
```ts
|
||||
export { kubernetesPlugin as default } from './plugin.ts';
|
||||
```
|
||||
|
||||
Done! Users of this plugin are now able to import your plugin package and register it in their backend using
|
||||
|
||||
```ts
|
||||
// packages/backend/src/index.ts
|
||||
import { kubernetesPlugin } from '@backstage/plugin-kubernetes-backend';
|
||||
backend.add(kubernetesPlugin);
|
||||
backend.add(import('@backstage/plugin-kubernetes-backend'));
|
||||
```
|
||||
|
||||
There's one thing missing that those sharp eyed readers might have noticed: the `clusterSupplier` option is missing from the original plugin. Let's add it and discuss the alternatives.
|
||||
|
||||
One alternative is to pass the `ClusterSupplier` in as options to the plugin, which is quick and easy but not very flexible, and also hard to evolve without introducing breaking changes as it changes the public API for the plugin. Having complex types passed in directly to the plugin also clutters the backend setup code and makes it harder to read.
|
||||
|
||||
Options are primarily used for simple configuration values that are not complex types. In this case we want to allow users to register their own `ClusterSupplier` implementations to the plugin. This is where the new backend system's [extension points](../architecture/05-extension-points.md) come in handy, but let's look at doing this with options first.
|
||||
One alternative is to make it possible to build the cluster supplier using static configuration. It could for example be that there is a selection of built-in implementations to choose from, or that the logic for how the `ClusterSupplier` is supposed to function is all determined by configuration, or a combination of the two. Using static configuration for customization is always the preferred option whenever it's possible. In this case, we could for example imagine that we would be able to configure our cluster supplier like this:
|
||||
|
||||
```ts
|
||||
/* omitted imports but they remain the same as above */
|
||||
|
||||
export interface KubernetesOptions {
|
||||
clusterSupplier?: KubernetesClustersSupplier;
|
||||
}
|
||||
|
||||
const kubernetesPlugin = createBackendPlugin((options: KubernetesOptions) => ({
|
||||
const kubernetesPlugin = createBackendPlugin({
|
||||
pluginId: 'kubernetes',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
/* omitted dependencies but they remain the same as above */
|
||||
},
|
||||
async init({ config, logger, catalogApi, discovery, http }) {
|
||||
// Note that in a real implementation this would be done by the `KubernetesBuilder` instead,
|
||||
// but here we've extracted it into a separate call to highlight the example.
|
||||
const configuredClusterSupplier = readClusterSupplierFromConfig(config);
|
||||
|
||||
const { router } = await KubernetesBuilder.createBuilder({
|
||||
config,
|
||||
logger,
|
||||
catalogApi,
|
||||
discovery,
|
||||
})
|
||||
.setClusterSupplier(configuredClusterSupplier)
|
||||
.build();
|
||||
http.use(router);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
There are however many types of customizations that are not possible to do with static configuration. In this case we want integrators to be able to create arbitrary implementations of the `ClusterSupplier` interface, which in the end requires an implementation through code. This is where the new backend system's [extension points](../architecture/05-extension-points.md) come in handy.
|
||||
|
||||
The new [extension points](../architecture/05-extension-points.md) API allows [modules](../architecture/06-modules.md) to add functionality into the backend plugin itself, in this case an additional `ClusterSupplier`. Let's look at how we could add support for installing custom suppliers using an extension point. This will allow integrators to build their own internal module with a custom `ClusterSupplier` implementation.
|
||||
|
||||
First we'll go ahead and create a `@backstage/plugin-kubernetes-node` package where we can define our extension point. A separate package is used to avoid direct dependencies on the plugin package itself. With the new package created, we define the extension point like this:
|
||||
|
||||
```ts
|
||||
import { createExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
|
||||
export interface KubernetesClusterSupplierExtensionPoint {
|
||||
setClusterSupplier(supplier: KubernetesClustersSupplier): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* An extension point that allows other plugins to set the cluster supplier.
|
||||
*/
|
||||
export const kubernetesClustersSupplierExtensionPoint =
|
||||
createExtensionPoint<KubernetesClusterSupplierExtensionPoint>({
|
||||
id: 'kubernetes.cluster-supplier',
|
||||
});
|
||||
```
|
||||
|
||||
For more information on how to design extension points, see the [extension points](../architecture/05-extension-points.md#extension-point-design) documentation.
|
||||
|
||||
Next we'll need to add support for this extension point to the Kubernetes backend plugin itself:
|
||||
|
||||
```ts
|
||||
/* omitted other imports but they remain the same as above */
|
||||
import { kubernetesClustersSupplierExtensionPoint } from '@backstage/plugin-kubernetes-node';
|
||||
|
||||
export const kubernetesPlugin = createBackendPlugin({
|
||||
pluginId: 'kubernetes',
|
||||
register(env) {
|
||||
let clusterSupplier: KubernetesClustersSupplier | undefined = undefined;
|
||||
|
||||
// We register the extension point with the backend, which allows modules to
|
||||
// register their own ClusterSupplier.
|
||||
env.registerExtensionPoint(kubernetesClustersSupplierExtensionPoint, {
|
||||
setClusterSupplier(supplier) {
|
||||
if (clusterSupplier) {
|
||||
throw new Error('ClusterSupplier may only be set once');
|
||||
}
|
||||
clusterSupplier = supplier;
|
||||
},
|
||||
});
|
||||
|
||||
env.registerInit({
|
||||
deps: {
|
||||
/* omitted dependencies but they remain the same as above */
|
||||
@@ -108,103 +177,7 @@ const kubernetesPlugin = createBackendPlugin((options: KubernetesOptions) => ({
|
||||
catalogApi,
|
||||
discovery,
|
||||
})
|
||||
.setClusterSupplier(options.clusterSupplier)
|
||||
.build();
|
||||
http.use(router);
|
||||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
The above would allow users to specify their own `ClusterSupplier` implementation to the plugin like this:
|
||||
|
||||
```ts
|
||||
backend.add(
|
||||
kubernetesPlugin({ clusterSupplier: new MyCustomClusterSupplier() }),
|
||||
);
|
||||
```
|
||||
|
||||
Just to echo what was said above, this is not a very flexible solution and will for example be problematic to keep backwards compatible if we start evolving the options to for example accept multiple suppliers or tweak the `ClusterSupplier` interface.
|
||||
|
||||
The new [extension points](../architecture/05-extension-points.md) API allows [modules](../architecture/06-modules.md) to add functionality into the backend plugin itself, in this case an additional `ClusterSupplier`.
|
||||
|
||||
The kubernetes backend plugin only supports one `ClusterSupplier` at this time but let's look at how we could add support for multiple suppliers using extension points. This allows users to install several modules that add their own `ClusterSupplier` implementations to the plugin like this:
|
||||
|
||||
```ts
|
||||
backend.add(kubernetesPlugin());
|
||||
backend.add(kubernetesGoogleContainerEngineClusterSupplier());
|
||||
backend.add(kubernetesElasticContainerEngine());
|
||||
```
|
||||
|
||||
Now let's look at how to implement this with extension points. First we need to define the extension point itself. As the extension point will be used by other modules, it's common practice to export these from a shared package so that they can be imported by other modules and plugins.
|
||||
|
||||
We'll go ahead and create a `@backstage/plugin-kubernetes-node` package for this and from there we'll export the extension point.
|
||||
|
||||
```ts
|
||||
import { createExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
|
||||
export interface KubernetesClusterSupplierExtensionPoint {
|
||||
addClusterSupplier(supplier: KubernetesClustersSupplier): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* An extension point that allows other plugins to add cluster suppliers.
|
||||
* @public
|
||||
*/
|
||||
export const kubernetesClustersSupplierExtensionPoint =
|
||||
createExtensionPoint<KubernetesClusterSupplierExtensionPoint>({
|
||||
id: 'kubernetes.cluster-supplier',
|
||||
});
|
||||
```
|
||||
|
||||
Now we can use this extension point in the kubernetes backend plugin to register the extension point for modules to use.
|
||||
|
||||
```ts
|
||||
import { kubernetesClustersSupplierExtensionPoint, KubernetesClusterSupplierExtensionPoint } from '@backstage/plugin-kubernetes-node';
|
||||
|
||||
// Our internal implementation of the extension point, should not be exported.
|
||||
class ClusterSupplier implements KubernetesClusterSupplierExtensionPoint {
|
||||
private clusterSuppliers: KubernetesClustersSupplier | undefined;
|
||||
|
||||
// This method is private and only used internally to retrieve the registered supplier.
|
||||
getClusterSupplier() {
|
||||
return this.clusterSuppliers;
|
||||
}
|
||||
|
||||
addClusterSupplier(supplier: KubernetesClustersSupplier) {
|
||||
// We can remove this check once the plugin support multiple suppliers.
|
||||
if(this.clusterSuppliers) {
|
||||
throw new Error('Multiple Kubernetes cluster suppliers is not supported at this time');
|
||||
}
|
||||
this.clusterSuppliers = supplier;
|
||||
}
|
||||
}
|
||||
|
||||
export const kubernetesPlugin = createBackendPlugin({
|
||||
pluginId: 'kubernetes',
|
||||
register(env) {
|
||||
const extensionPoint = new ClusterSupplier();
|
||||
// We register the extension point with the backend, which allows modules to
|
||||
// register their own ClusterSupplier.
|
||||
env.registerExtensionPoint(
|
||||
kubernetesClustersSupplierExtensionPoint,
|
||||
extensionPoint,
|
||||
);
|
||||
|
||||
env.registerInit({
|
||||
deps: {
|
||||
... omitted ...
|
||||
},
|
||||
async init({ config, logger, catalogApi, discovery, http }) {
|
||||
const { router } = await KubernetesBuilder.createBuilder({
|
||||
config,
|
||||
logger,
|
||||
catalogApi,
|
||||
discovery,
|
||||
})
|
||||
// We pass in the registered supplier from the extension point.
|
||||
.setClusterSupplier(extensionPoint.getClusterSupplier())
|
||||
.setClusterSupplier(clusterSupplier)
|
||||
.build();
|
||||
http.use(router);
|
||||
},
|
||||
@@ -213,24 +186,33 @@ export const kubernetesPlugin = createBackendPlugin({
|
||||
});
|
||||
```
|
||||
|
||||
And that's it! Modules can now be built that add clusters into to the kubernetes backend plugin, here's an example of a module that adds a `GoogleContainerEngineSupplier` to the kubernetes backend.
|
||||
And that's it! Modules can now be built that add clusters into to the kubernetes backend plugin, here's an example of a module that adds a `GoogleContainerEngineSupplier` to the kubernetes backend:
|
||||
|
||||
```ts
|
||||
import { kubernetesClustersSupplierExtensionPoint } from '@backstage/plugin-kubernetes-node';
|
||||
|
||||
export const kubernetesGoogleContainerEngineClusterSupplier =
|
||||
createBackendModule({
|
||||
pluginId: 'kubernetes',
|
||||
moduleId: 'gke.supplier',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
supplier: kubernetesClustersSupplierExtensionPoint,
|
||||
},
|
||||
async init({ supplier }) {
|
||||
supplier.addClusterSupplier(new GoogleContainerEngineSupplier());
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
// This is a custom implementation of the ClusterSupplier interface.
|
||||
import { GoogleContainerEngineSupplier } from './GoogleContainerEngineSupplier';
|
||||
|
||||
export default createBackendModule({
|
||||
pluginId: 'kubernetes',
|
||||
moduleId: 'gke.supplier',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
supplier: kubernetesClustersSupplierExtensionPoint,
|
||||
},
|
||||
async init({ supplier }) {
|
||||
supplier.setClusterSupplier(new GoogleContainerEngineSupplier());
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The above module can then be installed by the integrator alongside the kubernetes backend plugin:
|
||||
|
||||
```ts
|
||||
backend.add(import('@backstage/plugin-kubernetes-backend'));
|
||||
backend.add(import('@internal/gke-cluster-supplier'));
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user