Create a Tanzu Developer Portal plug-in

This topic teaches you how to create a Tanzu Developer Portal plug-in by wrapping an existing Backstage plug-in. After you create a Tanzu Developer Portal plug-in, you can build a customized Tanzu Developer Portal with Configurator.

Prerequisites

Meet the following prerequisites before creating a Tanzu Developer Portal plug-in.

Software

Ensure that you have the following software installed locally to develop a Tanzu Developer Portal plug-in:

  • Node 16: nvm is recommended. For how to install nvm, see the nvm GitHub repository. For how to install a specific version of nvm, see the NodeJS documentation.
  • yarn v1
  • (Optional) A UNIX-based OS: If you use Windows, you must find alternatives to some commands in this topic.

A Backstage plug-in in an accessible npm registry

Ensure that the Backstage plug-in you want to wrap is in an npm registry. You can use your own private registry or a public registry, such as npm JS. Both your development machine and your Tanzu Application Platform cluster must have access to the registry.

This topic tells you, by way of example, how to wrap the Backstage TechInsights plug-in. This plug-in consists of back-end and front-end components, both of which are available on npm JS:

Set up a development environment

This topic tells you how to create two Tanzu Developer Portal plug-ins by wrapping the @backstage/plugin-tech-insights and @backstage/plugin-tech-insights-backend Backstage plug-ins. You can create a separate repository for each of these plug-ins, but it’s easier and simpler to do the work for both in a single monorepo.

Generate a Backstage app for the monorepo

This topic describes how to use the Backstage tools @backstage/create-app and backstage-cli to manage your monorepo. The Backstage tools make managing packages easier. However, you will not develop a traditional Backstage app, and you will remove some portions of generated code later.

  1. This tutorial uses plugin-wrappers as the app name. Run the create-app script and, when prompted, enter a name for your app:

    npx @backstage/[email protected] --skip-install
    

    @backstage/create-app v0.5.2 is used because the Tanzu Developer Portal version that ships with Tanzu Application Platform v1.7 uses Backstage v1.15.0. Backstage v1.15.0 uses @backstage/create-app v0.5.2. For more information, see the Backstage version manifest.

    Important

    Ensure that you use the correct versions of dependencies for your Tanzu Application Platform version. Use the Backstage version compatibility reference table to find which versions of Backstage dependencies work with your version of Tanzu Application Platform.

    The --skip-install flag tells the script to not run yarn install. This is because you will remove the unnecessary dependencies that would have been needed if you were building a traditional Backstage app before installing your dependencies.

    The create-app command scaffolds a Backstage project structure under a directory matching your project name.

  2. Run:

    cd APP-NAME
    

    Where APP-NAME is your application name. For example, plugin-wrappers.

Remove some dependencies

To remove unnecessary dependencies you must:

  1. Remove the packages directory by running:

    rm -rf packages/
    

    The packages directory contains a scaffolded Backstage app and backend, which are only necessary for a traditional Backstage app.

  2. Remove the packages directory from the yarn workspaces by deleting the "packages/*" line within the workspaces attribute in package.json. For example:

    diff --git a/package.json b/package.json
    index 00d64c9..77f38f3 100644
    --- a/package.json
    +++ b/package.json
    @@ -24,7 +24,6 @@
      },
      "workspaces": {
        "packages": [
    -      "packages/*",
          "plugins/*"
        ]
      },
    
  3. Install the dependencies by running:

    yarn install --ignore-engines
    

    This command installs backstage-cli and a few other dependencies. The --ignore-engines flag is needed because a transitive dependency is expecting Node v18, but this Tanzu Developer Portal version currently only supports Node v16.

Create the Tech Insights front-end Tanzu Developer Portal plug-in

Now that you have an environment to develop your Tanzu Developer Portal plug-ins, you can begin wrapping Backstage plug-ins. You will start with the Tech Insight front-end plug-in.

Generate a front-end plug-in

This section describes how to generate a front-end plug-in:

  1. Generate a front-end plug-in by running the following command, replacing PACKAGE-NAMESPACE with your namespace (for example, @mycompany):

    yarn backstage-cli new --select plugin --option id=tech-insights-wrapper --scope PACKAGE-NAMESPACE --no-private
    
    Important

    The yarn install step of the previous command fails because of a Node version issue. This is handled in a later step.

    Here is a summary of what the backstage-cli new script does:

    • --select plugin creates a front-end plug-in
    • --option id=tech-insights-wrapper names the plug-in tech-insights-wrapper
    • --scope PACKAGE-NAMESPACE scopes the package under the PACKAGE-NAMESPACE namespace
    • --no-private sets the package to public
  2. Open the plugins/tech-insights-wrapper/package.json to see how these options were mapped to the generated package.json.

Add dependencies for the front-end plug-in

Update your dependencies for the specific Backstage plug-in you want to wrap:

  1. Replace the dependencies in the package.json with:

    ...
    "dependencies": {
     "@backstage/plugin-tech-insights": "0.3.11",
     "@backstage/plugin-catalog": "1.11.2",
     "@vmware-tanzu/core-common": "1.0.0",
     "@vmware-tanzu/core-frontend": "1.0.0"
    },
    ...
    
  2. The dependency on @backstage/plugin-tech-insights is obvious, but verify the version is compatible with your Tanzu Application Platform version by reading the Backstage version compatibility table and checking the version listed in the Backstage Manifest file for your specific Tanzu Application Portal version.

  3. @backstage/plugin-catalog is needed for a UI component that you use later. Verify its version by using the Backstage version compatibility table.

  4. Verify that you are using the correct versions of @vmware-tanzu/core-common and @vmware-tanzu/core-frontend by cross-referencing the dependency name with your Tanzu Application Platform version in the Tanzu Developer Portal plug-in libraries compatibility tables. You use @vmware-tanzu/core-common and @vmware-tanzu/core-frontend later for integrating the Backstage plug-in with Tanzu Developer Portal.

  5. Install the dependencies you added by running:

    cd plugins/tech-insights-wrapper
    yarn install --ignore-engines
    

Remove unnecessary code for the front-end plug-in

The backstage-cli new command created example code that you don’t need. Remove this code and start with an empty src directory by running:

rm -rf dev/ src/ && mkdir src

Wrap the Backstage front-end plug-in

  1. Read the documentation for @backstage/plugin-tech-insights. You will see that to use this Backstage plug-in, you must edit the content of the serviceEntityPage constant. Because you do not have access to the Tanzu Developer Portal source code, you cannot change that constant directly. Instead, you must use a surface to make the equivalent change.

  2. Create the file where you will use a surface to edit the serviceEntityPage constant by running:

    touch src/TechInsightsFrontendPlugin.tsx
    
  3. In the TechInsightsFrontendPlugin.tsx file, add the following code:

    import { EntityLayout } from '@backstage/plugin-catalog';
    import { EntityTechInsightsScorecardContent } from '@backstage/plugin-tech-insights';
    import {
     AppPluginInterface,
     SurfaceStoreInterface,
     EntityPageSurface,
    } from '@vmware-tanzu/core-frontend';
    import React from 'react';
    
    export const TechInsightsFrontendPlugin: AppPluginInterface =
     () => (context: SurfaceStoreInterface) => {
       context.applyTo(
         EntityPageSurface,
         (entityPageSurface) => {
           entityPageSurface.servicePage.addTab(
             <EntityLayout.Route path="/techinsights" title="TechInsights">
               <EntityTechInsightsScorecardContent
                 title="TechInsights Scorecard."
                 description="TechInsight's default fact-checkers"
               />
             </EntityLayout.Route>,
           );
         },
       );
     };
    

    Where:

    • context.applyTo is a function that takes the class of the surface you want to interact with, and a function that is passed the instance of that class.

    • The EntityPageSurface keeps track of tabs that appear on the service page. You add a new tab by calling entityPageSurface.servicePage.addTab and passing it the UI component you want it to render.

    • TechInsightsFrontendPlugin: AppPluginInterface = () => (context: SurfaceStoreInterface) => {} is boilerplate code that enables you to interact with the various front-end surfaces in Tanzu Developer Portal.

    • EntityPageSurface is one example of the many surfaces available in Tanzu Developer Portal. To discover all the surfaces currently available, see How to use surfaces. For surface API reference information, see API documentation for surfaces.

    This code accomplishes the same thing as the @backstage/plugin-tech-insights, but for an integration with Tanzu Developer Portal instead of a traditional Backstage app.

Expose and build the Tanzu Developer Portal front-end plug-in

To expose and then build the front-end plug-in:

  1. Create an index.ts file under the plugins/tech-insights-wrapper/src directory:

    touch src/index.ts
    
  2. In the index.ts file write:

    export { TechInsightsFrontendPlugin as plugin } from './TechInsightsFrontendPlugin';
    

    This exports TechInsightsFrontendPlugin in a way that enables Configurator to use your plug-in. You need to alias TechInsightsFrontendPlugin to plugin because the Tanzu Developer Portal Configurator expects compatible plug-ins to export a symbol with the name plugin.

  3. Build your Tanzu Developer Portal plug-in by running:

    yarn tsc && yarn build
    

You can now publish this plug-in to your npm registry. However, you cannot use the plug-in functions without the back-end portion.

Create the Tech Insights back-end Tanzu Developer Portal plug-in

Creating the back-end plug-in is very similar to the work you did for the front-end plug-in. This section does not describe in detail what is happening at each step except for where it differs from the previous work.

Generate a back-end plug-in

This describes how to generate a back-end plug-in.

From the root of your project, generate a back-end plug-in by running:

yarn backstage-cli new --select backend-plugin --option id=tech-insights-wrapper --scope PACKAGE-NAMESPACE --no-private

Where:

  • PACKAGE-NAMESPACE is the namespace for your package. For example, @mycompany.
  • --select backend-plugin tells the backstage-cli to generate a back-end plug-in.
  • --option id=tech-insights-wrapper provides the name for the plug-in. The ID you provide is the same as the front-end plug-in. backstage-cli automatically appends -backend to the directory and package-name of back-end plug-ins to prevent conflict with the front-end plug-in.

Add dependencies for the back-end plug-in

To add your dependencies for the specific Backstage plug-in you want to wrap:

  1. Update the dependencies in package.json as follows:

    "dependencies": {
     "@backstage/plugin-tech-insights-backend": "0.5.12",
     "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.30",
     "@vmware-tanzu/core-backend": "1.0.0",
     "express": "4.18.2"
    },
    
  2. Install your dependencies by running:

    cd plugins/tech-insights-wrapper-backend/
    yarn install --ignore-engines
    

Remove unnecessary code for the back-end plug-in

Remove the Backstage scaffolded example code by running:

rm -rf src/ && mkdir src

Wrap the Backstage back-end plug-in

  1. Within the src/ directory, create a file called TechInsightsBackendPlugin.ts by running:

    touch src/TechInsightsBackendPlugin.ts
    
  2. In TechInsightsBackendPlugin.ts, add the following code:

    import {
     createRouter,
     buildTechInsightsContext,
     createFactRetrieverRegistration,
     entityOwnershipFactRetriever,
     entityMetadataFactRetriever,
     techdocsFactRetriever,
    } from '@backstage/plugin-tech-insights-backend';
    import { Router } from 'express';
    import {
     JsonRulesEngineFactCheckerFactory,
     JSON_RULE_ENGINE_CHECK_TYPE,
    } from '@backstage/plugin-tech-insights-backend-module-jsonfc';
    import {
     BackendPluginInterface,
     BackendPluginSurface,
     PluginEnvironment,
    } from '@vmware-tanzu/core-backend';
    
    const ttlTwoWeeks = { timeToLive: { weeks: 2 } };
    
    export default async function createPlugin(
           env: PluginEnvironment,
    ): Promise<Router> {
     const techInsightsContext = await buildTechInsightsContext({
       logger: env.logger,
       config: env.config,
       database: env.database,
       discovery: env.discovery,
       tokenManager: env.tokenManager,
       scheduler: env.scheduler,
       factRetrievers: [
         createFactRetrieverRegistration({
           cadence: '0 */6 * * *', // Run every 6 hours - https://crontab.guru/#0_*/6_*_*_*
           factRetriever: entityOwnershipFactRetriever,
           lifecycle: ttlTwoWeeks,
         }),
         createFactRetrieverRegistration({
           cadence: '0 */6 * * *',
           factRetriever: entityMetadataFactRetriever,
           lifecycle: ttlTwoWeeks,
         }),
         createFactRetrieverRegistration({
           cadence: '0 */6 * * *',
           factRetriever: techdocsFactRetriever,
           lifecycle: ttlTwoWeeks,
         }),
       ],
       factCheckerFactory: new JsonRulesEngineFactCheckerFactory({
         logger: env.logger,
         checks: [
           {
             id: 'groupOwnerCheck',
             type: JSON_RULE_ENGINE_CHECK_TYPE,
             name: 'Group Owner Check',
             description:
                     'Verifies that a Group has been set as the owner for this entity',
             factIds: ['entityOwnershipFactRetriever'],
             rule: {
               conditions: {
                 all: [
                   {
                     fact: 'hasGroupOwner',
                     operator: 'equal',
                     value: true,
                   },
                 ],
               },
             },
           },
           {
             id: 'titleCheck',
             type: JSON_RULE_ENGINE_CHECK_TYPE,
             name: 'Title Check',
             description:
                     'Verifies that a Title, used to improve readability, has been set for this entity',
             factIds: ['entityMetadataFactRetriever'],
             rule: {
               conditions: {
                 all: [
                   {
                     fact: 'hasTitle',
                     operator: 'equal',
                     value: true,
                   },
                 ],
               },
             },
           },
           {
             id: 'techDocsCheck',
             type: JSON_RULE_ENGINE_CHECK_TYPE,
             name: 'TechDocs Check',
             description:
                     'Verifies that TechDocs has been enabled for this entity',
             factIds: ['techdocsFactRetriever'],
             rule: {
               conditions: {
                 all: [
                   {
                     fact: 'hasAnnotationBackstageIoTechdocsRef',
                     operator: 'equal',
                     value: true,
                   },
                 ],
               },
             },
           },
         ],
       }),
     });
    
     return await createRouter({
       ...techInsightsContext,
       logger: env.logger,
       config: env.config,
     });
    }
    
    export const TechInsightsBackendPlugin: BackendPluginInterface =
     () => surfaces =>
       surfaces.applyTo(BackendPluginSurface, backendPluginSurface => {
         backendPluginSurface.addPlugin({
           name: 'tech-insights',
           pluginFn: createPlugin,
         });
       });
    

    The majority of this code comes from the npm JS documentation. The Backstage plug-in documentation instructs you to create a constant for techInsightsEnv and then configure the router by using apiRouter.use('/tech-insights', await techInsights(techInsightsEnv)) all in the Backstage source code. Because you are unable to edit the source code of Tanzu Developer Portal, this code accomplishes the same thing by:

    • Getting an instance of BackendPluginSurface. This surface keeps track of all the back-end plug-ins.
    • Adding your plug-in by using the addPlugin function. The name argument is used to configure the path in the router.

Expose and build the Tanzu Developer Portal back-end plug-in

To expose and then build the back-end plug-in:

  1. Create an index.ts file by running:

    touch src/index.ts
    
  2. In the index.ts file, write:

    export { TechInsightsBackendPlugin as plugin } from './TechInsightsBackendPlugin';
    

    This exposes the Tanzu Developer Portal plug-in.

  3. Build your plug-in by running:

    yarn tsc && yarn build
    

Next steps

You can now publish your Tanzu Developer Portal plug-ins to your registry of choice. If you want to publish your plug-ins to a private registry, see Configure the Configurator with a private registry.

After publishing your plug-ins, you can build a customized Tanzu Developer Portal with Configurator.

check-circle-line exclamation-circle-line close-line
Scroll to top icon