Skip to content

Conversation

@rrosatti
Copy link

Add Konflux Plugin for Backstage

Overview

This PR is adding a Backstage integration plugin for Konflux (Red Hat's application delivery platform). The plugin aggregates and displays Kubernetes resources (Applications, Components, PipelineRuns, Releases) from multiple clusters and namespaces, providing a unified view of Konflux resources within Backstage.

Alpha Testing Phase

Once this plugin is merged and integrated into RHDH, it will enter an "alpha testing phase" on the InScope Backstage instance. During this period, we'll gather feedback from early users and iterate on improvements. The plugin is being developed in rhdh-plugins repo (rather than backstage/community-plugins) to enable faster iteration cycles during this testing period. Once alpha testing is complete and we've gathered sufficient feedback, we plan to polish the plugin and make it generally available.

Fixes

The main Epic ticket has several smaller tickets to help tracking the work :)

Fixes https://issues.redhat.com/browse/KFLUXUI-791
Fixes https://issues.redhat.com/browse/KFLUXUI-815
Fixes https://issues.redhat.com/browse/KFLUXUI-816
Fixes https://issues.redhat.com/browse/KFLUXUI-817
Fixes https://issues.redhat.com/browse/KFLUXUI-836
Fixes https://issues.redhat.com/browse/KFLUXUI-837
Fixes https://issues.redhat.com/browse/KFLUXUI-840
Fixes https://issues.redhat.com/browse/KFLUXUI-841

For a broader feature description see https://issues.redhat.com/browse/KONFLUX-8254

Architecture

The plugin consists of three packages:

  • konflux-common: shared types, models, and utilities (PipelineRun, Application, Component, Release types, configuration parsing, pagination utilities)
  • konflux-backend: backend service that fetches and aggregates resources from Kubernetes clusters via the Kubernetes API and Kubearchive
  • konflux: frontend React components (KonfluxPage, KonfluxCIPage, KonfluxStatus, LatestReleases)

Key Features

  • multi-cluster aggregation: fetches resources from multiple Kubernetes clusters in parallel
  • Kubearchive integration: falls back to archived resources when the Kubernetes API is exhausted
  • pagination: handles pagination across multiple sources with continuation tokens
  • application filtering: filters resources by Application name
  • error handling: tracks errors per cluster/namespace
  • Subcomponent support: aggregates resources across all subcomponents with fallback to main entity

How It Works

  1. configuration: combines cluster configuration from app-config.yaml with entity-specific configuration from catalog annotations (konflux-ci.dev/clusters)
  2. resource fetching: backend aggregates resources across configured clusters/namespaces, falling back to Kubearchive when needed
  3. display: frontend components display resources in dedicated tabs (Konflux, CI/CD) or entity overview cards, controlled by entity annotations (konflux-ci.dev/overview, konflux-ci.dev/konflux, konflux-ci.dev/ci)

Visual References

Demo Video

This is a short demo video that goes through all the components available with the Konflux plugin, including navigation between tabs and demonstration of filtering/pagination features.

short-demo.mp4

Screenshots

Entity Overview with Konflux Components (KonfluxStatus and KonfluxLatestReleases)
![Entity Overview tab showing KonfluxStatus and KonfluxLatestReleases components integrated into the standard Backstage entity overview]

1-overview-page

Konflux Tab - Applications and Components
![KonfluxPage displaying ApplicationsList and ComponentsList with filtering and pagination controls]

4-konflux-page-applications-table

5-konflux-page-components-table

CI/CD Tab - Pipeline Runs and Commits
![KonfluxCIPage showing PipelineRunsList and CommitsList with filtering and pagination controls]

2-cicd-page-plrs-table
3-cicd-page-commits-table

Configuration

The plugin requires configuration in two places: app-config.yaml for cluster configuration and entity annotations for feature enablement and resource mapping.

app Configuration (app-config.yaml)

Configure clusters and authentication in the backend configuration:

konflux:
  authProvider: serviceAccount # serviceAccount | oidc | impersonationHeaders
  clusters:
    cluster-name-1:
      apiUrl: https://api.cluster1.example.com
      uiUrl: https://konflux-ui.cluster1.example.com
      kubearchiveApiUrl: https://kubearchive-api.cluster1.example.com  # optional
      serviceAccountToken: <token>  # required for serviceAccount or impersonationHeaders
    cluster-name-2:
      apiUrl: https://api.cluster2.example.com
      uiUrl: https://konflux-ui.cluster2.example.com
      serviceAccountToken: <token>

Entity Configuration (catalog-info.yaml)

Entities can enable Konflux features via annotations:

Feature Control Annotations:

  • konflux-ci.dev/overview: 'true' - shows Konflux components in Overview tab
  • konflux-ci.dev/konflux: 'true' - enables Konflux tab
  • konflux-ci.dev/ci: 'true' - enables CI/CD tab

Cluster Configuration Annotation:

  • konflux-ci.dev/clusters - cluster/namespace/applications configuration (YAML format)

Example Entity Configuration:

---
kind: Component
apiVersion: backstage.io/v1alpha1
metadata:
  name: amazing-component
  annotations:
    konflux-ci.dev/overview: 'true'
    konflux-ci.dev/konflux: 'true'
    konflux-ci.dev/ci: 'true'
spec:
  type: service
  lifecycle: experimental
  owner: user:guest

---
kind: Component
apiVersion: backstage.io/v1alpha1
metadata:
  name: amazing-component-subcomponent-a
  annotations:
    konflux-ci.dev/overview: 'true'
    konflux-ci.dev/konflux: 'true'
    konflux-ci.dev/ci: 'true'
    konflux-ci.dev/clusters: |
      - cluster: cluster-name-1
        namespace: namespace-a
        applications:
          - application-a
          - application-b
spec:
  subcomponentOf: amazing-component
  type: service
  lifecycle: experimental
  owner: user:guest

Note: when subcomponents exist, only their cluster configurations are used. If no subcomponents exist, the main entity's configuration is used as a fallback.

Testing

  • ✅ all plugins build successfully
  • ✅ linting passes
  • ✅ workspace includes example entities and configuration
  • ✅ app starts and integrates with Backstage
  • ⚠️ OIDC authentication: code implemented and follows Backstage patterns, but not tested due to our OCP clusters not having the required version to support authentication with external OIDC providers.

Note: this PR adds the complete konflux plugin workspace including all three packages, configuration, and documentation ☕️

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or Updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)

@rhdh-gh-app
Copy link

rhdh-gh-app bot commented Nov 19, 2025

Changed Packages

Package Name Package Path Changeset Bump Current Version
app workspaces/konflux/packages/app none v0.0.0
backend workspaces/konflux/packages/backend none v0.0.0
@red-hat-developer-hub/backstage-plugin-konflux-backend workspaces/konflux/plugins/konflux-backend none v1.0.0
@red-hat-developer-hub/backstage-plugin-konflux-common workspaces/konflux/plugins/konflux-common none v0.1.0
@red-hat-developer-hub/backstage-plugin-konflux workspaces/konflux/plugins/konflux none v0.1.0

Comment on lines +38 to +58
export function getSubcomponentsWithFallback(
relatedEntities: Entity[] | null | undefined,
mainEntity: Entity,
): Entity[] {
if (!relatedEntities?.length) {
return [mainEntity];
}

const filtered = relatedEntities.filter(
e =>
e.metadata.name !== 'guest' &&
e.relations?.some(rel => rel?.type === 'partOf'),
);

// fallback to main entity if no valid subcomponents found
if (!filtered.length) {
return [mainEntity];
}

return filtered;
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: when subcomponents exist, we only use their cluster configs and ignore the main entity's config. falls back to main entity only if no subcomponents are found.

);

// case 1: continue from Kubearchive (k8s already exhausted)
if (kubearchiveToken && !k8sToken && hasKubearchive) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kubearchive fallback: if K8s is exhausted, automatically continue from Kubearchive. So, frontend only sees continuation tokens and doesn't need to know which source is used

Comment on lines +173 to +245
// decode continuation token to get pagination state for each source
let paginationState: PaginationState = {};
const isLoadMoreRequest = !!validatedFilters?.continuationToken;

const userId = userEntityRef || userEmail || 'unknown';

if (validatedFilters?.continuationToken) {
try {
paginationState = decodeContinuationToken(
validatedFilters.continuationToken,
userId,
);
} catch (error) {
this.konfluxLogger.error('Failed to decode continuation token', error, {
userId,
entityRef,
resource,
});
throw error instanceof Error
? error
: new Error('Invalid continuation token');
}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

each source (cluster:namespace) maintains separate pagination state with tokens for both K8s API and karchive

const sourceState = paginationState[sourceKey] || {};
const hasAnyToken = sourceState.k8sToken || sourceState.kubearchiveToken;

if (isLoadMoreRequest && !hasAnyToken) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skip exhausted sources (no tokens) in "Load More" requests to avoid unnecessary API calls

}
};

export const determineClusterNamespaceCombinations = async (
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

combines app-config.yaml cluster configs with entity annotations. Groups by subcomponent+cluster+namespace and merges applications lists when multiple subcomponents reference the same cluster:namespace

@@ -0,0 +1,79 @@
{
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for those unfamiliar with Backstage framework/architecture: the packages/ directory contains a "local development environment" (app + backend) for building and testing the plugin. This is standard Backstage workspace structure and not part of the plugin itself - it's only used for local development/testing :)

@rrosatti rrosatti marked this pull request as ready for review December 3, 2025 12:32
@rrosatti rrosatti requested review from a team as code owners December 3, 2025 12:32
@github-actions
Copy link
Contributor

This PR has been automatically marked as stale because it has not had recent activity from the author. It will be closed if no further activity occurs. If the PR was closed and you want it re-opened, let us know and we'll re-open the PR so that you can continue the contribution!

@github-actions github-actions bot added the stale label Dec 17, 2025
@rrosatti rrosatti changed the title [WIP] Add Konflux plugin Add Konflux plugin Dec 18, 2025
@github-actions github-actions bot removed the stale label Dec 18, 2025
@rrosatti rrosatti force-pushed the konflux-plugin branch 2 times, most recently from 09c22ee to 453afd9 Compare December 22, 2025 09:29
cluster1:
apiUrl: https://api.cluster1.example.com
uiUrl: https://ui.cluster1.example.com
kubearchiveApiUrl: https://archive.cluster1.example.com
Copy link

@testcara testcara Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we use something like ${SA_TOKEN_CLUSTER_NAME} to keep consistent usage with github_token?
We have several parameters here. For me, i create a .env and set all the variables then 'set -a; source .env; set +a'. then start the app.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also i checked some other plugins, in their gitignore files, they ignore '.env'. i guess they recommend this way.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the code to use "env vars" naming to keep it consistent!

options?: {
pageSize?: number;
pageToken?: string;
filter?: string; // TODO: handle filter

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether the // TODO comments are valid?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. I left this TODO for a future improvement :)


- **serviceAccount**: Uses service account tokens
- **impersonationHeaders**: Uses Kubernetes impersonation headers (validates userEmail)
- **[UNSTABLE / NOT TESTED]** **oidc**: Uses OIDC tokens from the frontend (passed via X-OIDC-Token header)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to test this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately, I don't think so 😕 it requires upgrading to OCP 4.19. That's a message from Gal in a discussion on Slack:

"we currently use user impersonation for the Konflux UI but we are going to stop doing it after upgrading to OCP 4.19 which allows to integrate with external OIDC providers. With this approach the same token would be recognized by both Konflux proxy and the api server."

For dev/stage we can use serviceAccount and impersonationHeaders. But, for production, we'll have to wait for upgrading to OCP 4.19, so we can use OIDC auth.

If you have some ideas on how we could test this oidc provider approach in a dev/local environment, pls let me know :)

}),
);

const results = await Promise.all(fetchPromises);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

promise all? whether we have potential performance problems here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it should be fine to use Promise.all() here. I'd propose to keep it this way and once we have this in alpha testing, we can gather more info regarding if it's causing issues and could be improved. wdyt? :)

Copy link

@testcara testcara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally LGTM, i just left few tiny comments. Thank you for the great patch.

Copy link

@testcara testcara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally lgtm, according to testing, the function works well.
For me, it is ready for user testing as we plan.

* add workspace package.json
* add tsconfig.json
* add app-config.yaml template
* create plugins/ directory structure
- add konflux cluster configuration to app-config.yaml
- add example entities and org data
- add config.d.ts file with konflux configuration type definition
- add common types (PipelineRun, Application, Component, Release)
- add configuration parsing utilities
- add pagination constants and utilities
- add entity processing utilities
- add subcomponent utilities
- add README
…tion

- add KonfluxService for aggregating resources across clusters
- add ResourceFetcherService for Kubernetes API and Kubearchive integration
- add KubearchiveService for "archived resource fallback"
- add router with /entity/:entityRef/resource/:resource endpoint
- add configuration helpers and validation
- add error extraction and logging utilities
- support multiple auth methods (serviceAccount, impersonationHeaders)
  - IMPORTANT: OIDC still not tested
- add README
- add KonfluxPage component for Applications and Components resources
- add KonfluxCIPage component for Commits and PipelineRuns resources
- add KonfluxStatus component for status display
- add LatestReleases component
- add resource hooks (useApplications, useComponents, usePipelineruns, useReleases)
- add filtering and pagination utilities
- add table components with PatternFly styling
- add main README.md
…space

- add app package with Backstage app setup
- add backend package with backend server configuration
- configure app routes and entity pages
- set up authentication providers
- add dependencies on konflux plugins
- integrate konflux plugin into Backstage app
@sonarqubecloud
Copy link

Copy link

@hopehadfield hopehadfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay in reviewing. Overall very nice work on the plugin, I just left a few comments to address before we can go ahead and merge 😄🚀

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file isn't necessary for this repository, only for the backstage/community-plugins repo.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should instead exist within the plugin packages themselves, either in workspaces/konflux/plugins/konflux-common or in both the frontend and backend package. Also, the visibility annotations should be double-checked (i.e. serviceAccountToken should probably be @visibility secret)

Comment on lines +12 to +14
"backstage": {
"role": "backend"
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"backstage": {
"role": "backend"
},
"backstage": {
"role": "backend-plugin",
"pluginId": "konflux",
"pluginPackage": "@red-hat-developer-hub/backstage-plugin-konflux-backend",
"pluginPackages": [
"@red-hat-developer-hub/backstage-plugin-konflux",
"@red-hat-developer-hub/backstage-plugin-konflux-backend",
"@red-hat-developer-hub/backstage-plugin-konflux-common"
]
},

"prettier --write"
]
},
"dependencies": {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider cleaning these up, most of these likely belong in or are already in the individual plugin packages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants