SonarQube logo

Backstage SonarQube Plugin

Created by SDA-SE

SonarQube helps you keep code quality and security in check. It runs static analysis in CI and flags bugs, vulnerabilities, code smells, coverage gaps, and duplications. Teams use it to enforce a quality gate so new code meets a standard before it ships.

The SonarQube Backstage plugin brings those results into your developer portal. It adds a card to each entity that shows the latest analysis and the quality gate state. You can view bugs, vulnerabilities, code smells, coverage, duplications, and reviewed hotspots at a glance. It links straight to the SonarQube dashboard when you need detail. A full page view can list related entities with sortable measures across a system.

It works with SonarQube or SonarCloud. It supports multiple SonarQube instances through the standard project key annotation. This helps engineers see code health beside ownership, docs, and runtime signals. Common uses include surfacing failing gates in service catalog views, spotting risky hotspots across a domain, and tracking coverage trends during a release.

A screenshot of the SonarQube and SonarCloud plugin.

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 in the Backstage root folder

Copy
yarn --cwd packages/app add @backstage-community/plugin-sonarqube @backstage-community/plugin-sonarqube-react

Add the SonarQube card to the entity page

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

Copy
import React from 'react';
import Grid from '@material-ui/core/Grid';
import { EntityAboutCard, EntityLayout, EntitySwitch } from '@backstage/plugin-catalog';
import { RELATION_HAS_PART } from '@backstage/catalog-model';

import { EntitySonarQubeCard, SonarQubeRelatedEntitiesOverview } from '@backstage-community/plugin-sonarqube';
import { isSonarQubeAvailable } from '@backstage-community/plugin-sonarqube-react';

const MISSING_ANNOTATION_READ_MORE_URL =
  'https://backstage.io/docs/features/software-catalog/descriptor';

const overviewContent = (
  <Grid container spacing={3} alignItems="stretch">
    <Grid item md={6}>
      <EntityAboutCard variant="gridItem" />
    </Grid>

    <EntitySwitch>
      <EntitySwitch.Case if={isSonarQubeAvailable}>
        <Grid item md={6}>
          <EntitySonarQubeCard
            variant="gridItem"
            missingAnnotationReadMoreUrl={MISSING_ANNOTATION_READ_MORE_URL}
          />
        </Grid>
      </EntitySwitch.Case>
    </EntitySwitch>
  </Grid>
);

export const systemPage = (
  <EntityLayout>
    {/* your other routes */}

    <EntityLayout.Route path="/sonarqube" title="Code Quality">
      <SonarQubeRelatedEntitiesOverview relationType={RELATION_HAS_PART} entityKind="component" />
    </EntityLayout.Route>
  </EntityLayout>
);

Using the new frontend system

Add the feature to your packages/app/src/App.tsx

Copy
import { createApp } from '@backstage/app-defaults';
import sonarQubePlugin from '@backstage-community/plugin-sonarqube/alpha';

export const app = createApp({
  features: [
    sonarQubePlugin,
  ],
});

If you still use the classic plugin based App.tsx you can leave this out

Install the backend plugin using the classic backend system

Add the backend package

Copy
yarn --cwd packages/backend add @backstage-community/plugin-sonarqube-backend

Create the router file packages/backend/src/plugins/sonarqube.ts

Copy
import { createRouter } from '@backstage-community/plugin-sonarqube-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  return await createRouter({
    logger: env.logger,
    config: env.config,
    discovery: env.discovery,
    tokenManager: env.tokenManager,
  });
}

Register the router in packages/backend/src/index.ts

Copy
// add this near the other plugin imports
import sonarqube from './plugins/sonarqube';

// inside the main function after you create the env for the backend
const sonarqubeEnv = useHotMemoize(module, () => createEnv('sonarqube'));

// add this where you wire up other routers
apiRouter.use('/sonarqube', await sonarqube(sonarqubeEnv));

Add basic config in app-config.yaml

Copy
sonarqube:
  instances:
    - name: default
      baseUrl: https://sonarcloud.io
      # organization is only for SonarCloud
      # organization: your_org
      # token is optional
      # token: ${SONARQUBE_TOKEN}

Self hosted example

Copy
sonarqube:
  instances:
    - name: default
      baseUrl: https://sonarqube.mycompany.example
      # token is optional
      # token: ${SONARQUBE_TOKEN}

The frontend uses the instance name from the entity annotation. If you omit the instance in the annotation it uses default

About the new backend system

If your app already uses the new backend system add the SonarQube backend plugin using your normal backend builder. Install the same backend package shown above. Then register the SonarQube backend plugin in your backend composition so it exposes the api route for the frontend. Keep the same sonarqube config shown above

Add the entity annotation

Add this to the catalog info file of each entity you want to show in SonarQube

Copy
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: my-service
  annotations:
    sonarqube.org/project-key: YOUR_INSTANCE_NAME/YOUR_PROJECT_KEY
spec:
  type: service
  owner: team-a
  lifecycle: production

You can drop YOUR_INSTANCE_NAME if you want to use the default instance

Things to Know

Notes

In order for the backstage integration to work we must first generate our api key. These can be found from:

These will then be used in our app-config.yaml and subsequently picked up by backstage and allow it to talk to your sonar apps.

It is always important to base encode our tokens.

Copy
$ export SONARCLOUD_TOKEN="<YOUR_SONARCLOUD_TOKEN>"
$ export SONARQUBE_TOKEN="<YOUR_SONARQUBE_TOKEN>"

You can then add these token(s) to a .env file or keep it as an exported variable.

Set up Backstage in minutes with Roadie