From 3afdd1f8a1258105fefd83f9d405c1fccce9bc78 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Wed, 8 Jul 2026 09:23:14 -0400 Subject: [PATCH 01/13] feat(extensions): add source metadata to entities Signed-off-by: Hope Hadfield --- .../src/providers/BaseEntityProvider.test.ts | 172 ++++++++++++++++++ .../src/providers/BaseEntityProvider.ts | 32 +++- .../extensions-common/src/annotations.ts | 1 + 3 files changed, 202 insertions(+), 3 deletions(-) diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts index fd8b45a01fe..005e98c6f3a 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts @@ -19,6 +19,7 @@ import { LoggerService, SchedulerServiceTaskRunner, } from '@backstage/backend-plugin-api'; +import { ExtensionsAnnotation } from '@red-hat-developer-hub/backstage-plugin-extensions-common'; import { BaseEntityProvider } from './BaseEntityProvider'; import { JsonFileData } from '../types'; @@ -136,3 +137,174 @@ describe('BaseEntityProvider collision policy', () => { expect(entities).toHaveLength(2); }); }); + +describe('BaseEntityProvider.deriveCatalogSource', () => { + it('returns "primary" for paths in the main catalog-entities directory', () => { + expect( + BaseEntityProvider.deriveCatalogSource( + '/extensions/catalog-entities/plugin.yaml', + ), + ).toBe('primary'); + }); + + it('returns "primary" for paths without the extra/ segment', () => { + expect( + BaseEntityProvider.deriveCatalogSource('/extensions/plugins/foo.yaml'), + ).toBe('primary'); + }); + + it('returns the source name for paths under extra//', () => { + expect( + BaseEntityProvider.deriveCatalogSource( + '/extensions/extra/community/catalog-entities/plugin.yaml', + ), + ).toBe('community'); + }); + + it('returns the source name for a different extra source', () => { + expect( + BaseEntityProvider.deriveCatalogSource( + '/extensions/extra/partner/catalog-entities/plugins/plugin.yaml', + ), + ).toBe('partner'); + }); + + it('handles auto-derived subdirectory names with special characters', () => { + // imageRefToSubdirectory replaces /:@ with _ so names like this are common + expect( + BaseEntityProvider.deriveCatalogSource( + '/extensions/extra/quay.io_rhdh_index_1.10/catalog-entities/plugin.yaml', + ), + ).toBe('quay.io_rhdh_index_1.10'); + }); + + it('handles deeply nested files within a source directory', () => { + expect( + BaseEntityProvider.deriveCatalogSource( + '/extensions/extra/community/catalog-entities/nested/deep/plugin.yaml', + ), + ).toBe('community'); + }); + + it('handles Windows-style backslash paths', () => { + expect( + BaseEntityProvider.deriveCatalogSource( + 'C:\\extensions\\extra\\community\\catalog-entities\\plugin.yaml', + ), + ).toBe('community'); + }); +}); + +describe('BaseEntityProvider source metadata annotations', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('sets catalog-source to "primary" for entities from the main catalog root', () => { + const provider = new TestEntityProvider(taskRunner, undefined, logger); + const entity = createEntity({ metadata: { name: 'primary-plugin' } }); + + const entities = provider.getEntities([ + createFileData('/extensions/catalog-entities/plugin.yaml', entity), + ]); + + expect(entities).toHaveLength(1); + expect( + entities[0].metadata.annotations?.[ExtensionsAnnotation.CATALOG_SOURCE], + ).toBe('primary'); + }); + + it('sets catalog-source to the extra source name for entities under extra//', () => { + const provider = new TestEntityProvider(taskRunner, undefined, logger); + const entity = createEntity({ metadata: { name: 'community-plugin' } }); + + const entities = provider.getEntities([ + createFileData( + '/extensions/extra/community/catalog-entities/plugin.yaml', + entity, + ), + ]); + + expect(entities).toHaveLength(1); + expect( + entities[0].metadata.annotations?.[ExtensionsAnnotation.CATALOG_SOURCE], + ).toBe('community'); + }); + + it('sets distinct source annotations when entities come from different sources', () => { + const provider = new TestEntityProvider(taskRunner, undefined, logger); + const primaryPlugin = createEntity({ + metadata: { name: 'plugin-a' }, + }); + const communityPlugin = createEntity({ + metadata: { name: 'plugin-b' }, + }); + + const entities = provider.getEntities([ + createFileData( + '/extensions/catalog-entities/plugin-a.yaml', + primaryPlugin, + ), + createFileData( + '/extensions/extra/community/catalog-entities/plugin-b.yaml', + communityPlugin, + ), + ]); + + expect(entities).toHaveLength(2); + const sourceA = + entities[0].metadata.annotations?.[ExtensionsAnnotation.CATALOG_SOURCE]; + const sourceB = + entities[1].metadata.annotations?.[ExtensionsAnnotation.CATALOG_SOURCE]; + expect(sourceA).toBe('primary'); + expect(sourceB).toBe('community'); + }); + + it('preserves the winning entity source annotation on duplicate (first-wins)', () => { + const provider = new TestEntityProvider(taskRunner, undefined, logger); + const entity = createEntity({ metadata: { name: 'dup-plugin' } }); + + const entities = provider.getEntities([ + createFileData( + '/extensions/extra/community/catalog-entities/plugin.yaml', + entity, + ), + createFileData('/extensions/catalog-entities/plugin.yaml', entity), + ]); + + expect(entities).toHaveLength(1); + // First-wins: the community entity was seen first + expect( + entities[0].metadata.annotations?.[ExtensionsAnnotation.CATALOG_SOURCE], + ).toBe('community'); + }); + + it('sets correct distinct sources when entities share name but differ by namespace', () => { + const provider = new TestEntityProvider(taskRunner, undefined, logger); + const primaryEntity = createEntity({ + metadata: { name: 'shared-name' }, + }); + const communityEntity = createEntity({ + metadata: { name: 'shared-name', namespace: 'community' }, + }); + + const entities = provider.getEntities([ + createFileData('/extensions/catalog-entities/plugin.yaml', primaryEntity), + createFileData( + '/extensions/extra/community/catalog-entities/plugin.yaml', + communityEntity, + ), + ]); + + expect(entities).toHaveLength(2); + const sources = entities.map( + e => e.metadata.annotations?.[ExtensionsAnnotation.CATALOG_SOURCE], + ); + expect(sources).toContain('primary'); + expect(sources).toContain('community'); + }); +}); diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts index 12f8c4910a8..99a6b2c85d9 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts @@ -28,6 +28,7 @@ import { EntityProviderConnection, } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; +import { ExtensionsAnnotation } from '@red-hat-developer-hub/backstage-plugin-extensions-common'; import { readYamlFiles } from '../utils/file-utils'; import { JsonFileData } from '../types'; import path from 'path'; @@ -61,7 +62,30 @@ export abstract class BaseEntityProvider abstract getProviderName(): string; abstract getKind(): string; - private addProviderAnnotations(entity: T): T { + /** + * The default source identifier used for entities that do not reside + * under an `extra//` subdirectory — i.e. entities from the + * primary catalog index image. + */ + static readonly DEFAULT_CATALOG_SOURCE = 'primary'; + + /** + * Derives a catalog source identifier from a file path. + * + * When `install-dynamic-plugins` extracts extra catalog index images + * (via `EXTRA_CATALOG_INDEX_IMAGES`), entities are placed under + * `/extra//catalog-entities/…`. This method + * detects the `extra//` segment and returns `` as the + * source identifier. All other paths return `"primary"`. + */ + static deriveCatalogSource(filePath: string): string { + // Normalise to forward slashes so the regex works on all platforms. + const normalised = filePath.replace(/\\/g, '/'); + const match = normalised.match(/\/extra\/([^/]+)\//); + return match ? match[1] : BaseEntityProvider.DEFAULT_CATALOG_SOURCE; + } + + private addProviderAnnotations(entity: T, filePath: string): T { return { ...entity, metadata: { @@ -70,6 +94,8 @@ export abstract class BaseEntityProvider ...entity.metadata.annotations, [ANNOTATION_LOCATION]: `file:${this.getProviderName()}`, [ANNOTATION_ORIGIN_LOCATION]: `file:${this.getProviderName()}`, + [ExtensionsAnnotation.CATALOG_SOURCE]: + BaseEntityProvider.deriveCatalogSource(filePath), }, }, }; @@ -116,8 +142,8 @@ export abstract class BaseEntityProvider ); } - return Array.from(entitiesByEntityRef.values()).map(({ entity }) => - this.addProviderAnnotations(entity), + return Array.from(entitiesByEntityRef.values()).map( + ({ entity, filePath }) => this.addProviderAnnotations(entity, filePath), ); } diff --git a/workspaces/extensions/plugins/extensions-common/src/annotations.ts b/workspaces/extensions/plugins/extensions-common/src/annotations.ts index 8fa3e920285..d3f509c572f 100644 --- a/workspaces/extensions/plugins/extensions-common/src/annotations.ts +++ b/workspaces/extensions/plugins/extensions-common/src/annotations.ts @@ -19,4 +19,5 @@ */ export enum ExtensionsAnnotation { PRE_INSTALLED = 'extensions.backstage.io/pre-installed', + CATALOG_SOURCE = 'extensions.backstage.io/catalog-source', } From cc83bf2d533ae636d5c3ad62e43a00d904f4bda1 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Wed, 8 Jul 2026 11:39:44 -0400 Subject: [PATCH 02/13] chore: add docs Signed-off-by: Hope Hadfield --- workspaces/extensions/docs/catalog/plugins.md | 15 +++++++++++ .../README.md | 27 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/workspaces/extensions/docs/catalog/plugins.md b/workspaces/extensions/docs/catalog/plugins.md index 3827c873f84..e6c83d42323 100644 --- a/workspaces/extensions/docs/catalog/plugins.md +++ b/workspaces/extensions/docs/catalog/plugins.md @@ -229,6 +229,21 @@ spec: ## Annotations +### Catalog source + +Automatically set by the `BaseEntityProvider` to identify which catalog image each entity came from. The value is derived from the on-disk directory layout produced by `install-dynamic-plugins`: + +- Entities under the primary `catalog-entities/` tree get `"primary"` +- Entities under `extra//catalog-entities/` get `` as the source (matching the name from `EXTRA_CATALOG_INDEX_IMAGES`) + +This annotation is always set and should not be manually specified in YAML files. + +```yaml +metadata: + annotations: + extensions.backstage.io/catalog-source: 'primary' +``` + ### Support type for Core and Community plugins ```yaml diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/README.md b/workspaces/extensions/plugins/catalog-backend-module-extensions/README.md index 723d3225199..aa59f30d3c5 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/README.md +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/README.md @@ -59,6 +59,33 @@ extensions: directory: /path/to/custom/extensions ``` +### Multi-source catalog layout + +When the `install-dynamic-plugins` init container is configured with `EXTRA_CATALOG_INDEX_IMAGES`, it extracts additional catalog entities into subdirectories under `extra/`: + +``` +/ + catalog-entities/ # primary source + plugins/ + plugin-a.yaml + extra/ + community/ # extra source "community" + catalog-entities/ + plugins/ + plugin-b.yaml + partner/ # extra source "partner" + catalog-entities/ + plugins/ + plugin-c.yaml +``` + +The provider reads all YAML files recursively from the extensions directory and automatically sets the `extensions.backstage.io/catalog-source` annotation on each entity based on where it was found: + +- Entities from the primary `catalog-entities/` tree get `catalog-source: "primary"` +- Entities from `extra//catalog-entities/` get `catalog-source: ""` + +This allows the Extensions UI to identify which catalog each plugin came from (e.g. Red Hat, Community, partner). + ### Collision behavior When multiple YAML sources define the same entity identity (`kind:namespace/name`), the provider handles collisions as follows: From 2c335515e283097cb881c6ac554025856e0eb198 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Wed, 8 Jul 2026 11:56:11 -0400 Subject: [PATCH 03/13] chore: add changeset Signed-off-by: Hope Hadfield --- workspaces/extensions/.changeset/khaki-points-mate.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 workspaces/extensions/.changeset/khaki-points-mate.md diff --git a/workspaces/extensions/.changeset/khaki-points-mate.md b/workspaces/extensions/.changeset/khaki-points-mate.md new file mode 100644 index 00000000000..fb22a11ee01 --- /dev/null +++ b/workspaces/extensions/.changeset/khaki-points-mate.md @@ -0,0 +1,6 @@ +--- +'@red-hat-developer-hub/backstage-plugin-catalog-backend-module-extensions': minor +'@red-hat-developer-hub/backstage-plugin-extensions-common': minor +--- + +Add catalog source annotations to entities From a77f528c8b32cfc3204ebe86b1a29f03d199f22b Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Wed, 8 Jul 2026 13:15:44 -0400 Subject: [PATCH 04/13] chore: update api reports Signed-off-by: Hope Hadfield --- .../plugins/catalog-backend-module-extensions/report.api.md | 2 ++ workspaces/extensions/plugins/extensions-common/report.api.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md b/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md index 26ceca31a5e..a5cbabe5877 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md @@ -34,6 +34,8 @@ export abstract class BaseEntityProvider ); // (undocumented) connect(connection: EntityProviderConnection): Promise; + static readonly DEFAULT_CATALOG_SOURCE = 'primary'; + static deriveCatalogSource(filePath: string): string; // (undocumented) getEntities(allEntities: JsonFileData[]): T[]; // (undocumented) diff --git a/workspaces/extensions/plugins/extensions-common/report.api.md b/workspaces/extensions/plugins/extensions-common/report.api.md index 86e6a486ec5..451ff4ab274 100644 --- a/workspaces/extensions/plugins/extensions-common/report.api.md +++ b/workspaces/extensions/plugins/extensions-common/report.api.md @@ -96,6 +96,8 @@ export const EXTENSIONS_API_VERSION = 'extensions.backstage.io/v1alpha1'; // @public (undocumented) export enum ExtensionsAnnotation { + // (undocumented) + CATALOG_SOURCE = 'extensions.backstage.io/catalog-source', // (undocumented) PRE_INSTALLED = 'extensions.backstage.io/pre-installed', } From 87ae75cd0db2483c952264782c82a909a1682ef0 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Tue, 21 Jul 2026 11:42:32 -0400 Subject: [PATCH 05/13] use path.relative instead of regex in deriveCatalogSource Signed-off-by: Hope Hadfield --- .../src/providers/BaseEntityProvider.test.ts | 104 +++++++++++------- .../src/providers/BaseEntityProvider.ts | 34 ++++-- 2 files changed, 84 insertions(+), 54 deletions(-) diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts index 005e98c6f3a..ea2d8c35d69 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts @@ -139,17 +139,23 @@ describe('BaseEntityProvider collision policy', () => { }); describe('BaseEntityProvider.deriveCatalogSource', () => { + const root = '/extensions'; + it('returns "primary" for paths in the main catalog-entities directory', () => { expect( BaseEntityProvider.deriveCatalogSource( '/extensions/catalog-entities/plugin.yaml', + root, ), ).toBe('primary'); }); it('returns "primary" for paths without the extra/ segment', () => { expect( - BaseEntityProvider.deriveCatalogSource('/extensions/plugins/foo.yaml'), + BaseEntityProvider.deriveCatalogSource( + '/extensions/plugins/foo.yaml', + root, + ), ).toBe('primary'); }); @@ -157,6 +163,7 @@ describe('BaseEntityProvider.deriveCatalogSource', () => { expect( BaseEntityProvider.deriveCatalogSource( '/extensions/extra/community/catalog-entities/plugin.yaml', + root, ), ).toBe('community'); }); @@ -165,6 +172,7 @@ describe('BaseEntityProvider.deriveCatalogSource', () => { expect( BaseEntityProvider.deriveCatalogSource( '/extensions/extra/partner/catalog-entities/plugins/plugin.yaml', + root, ), ).toBe('partner'); }); @@ -174,6 +182,7 @@ describe('BaseEntityProvider.deriveCatalogSource', () => { expect( BaseEntityProvider.deriveCatalogSource( '/extensions/extra/quay.io_rhdh_index_1.10/catalog-entities/plugin.yaml', + root, ), ).toBe('quay.io_rhdh_index_1.10'); }); @@ -182,14 +191,7 @@ describe('BaseEntityProvider.deriveCatalogSource', () => { expect( BaseEntityProvider.deriveCatalogSource( '/extensions/extra/community/catalog-entities/nested/deep/plugin.yaml', - ), - ).toBe('community'); - }); - - it('handles Windows-style backslash paths', () => { - expect( - BaseEntityProvider.deriveCatalogSource( - 'C:\\extensions\\extra\\community\\catalog-entities\\plugin.yaml', + root, ), ).toBe('community'); }); @@ -204,13 +206,16 @@ describe('BaseEntityProvider source metadata annotations', () => { jest.restoreAllMocks(); }); + const root = '/extensions'; + it('sets catalog-source to "primary" for entities from the main catalog root', () => { const provider = new TestEntityProvider(taskRunner, undefined, logger); const entity = createEntity({ metadata: { name: 'primary-plugin' } }); - const entities = provider.getEntities([ - createFileData('/extensions/catalog-entities/plugin.yaml', entity), - ]); + const entities = provider.getEntities( + [createFileData('/extensions/catalog-entities/plugin.yaml', entity)], + root, + ); expect(entities).toHaveLength(1); expect( @@ -222,12 +227,15 @@ describe('BaseEntityProvider source metadata annotations', () => { const provider = new TestEntityProvider(taskRunner, undefined, logger); const entity = createEntity({ metadata: { name: 'community-plugin' } }); - const entities = provider.getEntities([ - createFileData( - '/extensions/extra/community/catalog-entities/plugin.yaml', - entity, - ), - ]); + const entities = provider.getEntities( + [ + createFileData( + '/extensions/extra/community/catalog-entities/plugin.yaml', + entity, + ), + ], + root, + ); expect(entities).toHaveLength(1); expect( @@ -244,16 +252,19 @@ describe('BaseEntityProvider source metadata annotations', () => { metadata: { name: 'plugin-b' }, }); - const entities = provider.getEntities([ - createFileData( - '/extensions/catalog-entities/plugin-a.yaml', - primaryPlugin, - ), - createFileData( - '/extensions/extra/community/catalog-entities/plugin-b.yaml', - communityPlugin, - ), - ]); + const entities = provider.getEntities( + [ + createFileData( + '/extensions/catalog-entities/plugin-a.yaml', + primaryPlugin, + ), + createFileData( + '/extensions/extra/community/catalog-entities/plugin-b.yaml', + communityPlugin, + ), + ], + root, + ); expect(entities).toHaveLength(2); const sourceA = @@ -268,13 +279,16 @@ describe('BaseEntityProvider source metadata annotations', () => { const provider = new TestEntityProvider(taskRunner, undefined, logger); const entity = createEntity({ metadata: { name: 'dup-plugin' } }); - const entities = provider.getEntities([ - createFileData( - '/extensions/extra/community/catalog-entities/plugin.yaml', - entity, - ), - createFileData('/extensions/catalog-entities/plugin.yaml', entity), - ]); + const entities = provider.getEntities( + [ + createFileData( + '/extensions/extra/community/catalog-entities/plugin.yaml', + entity, + ), + createFileData('/extensions/catalog-entities/plugin.yaml', entity), + ], + root, + ); expect(entities).toHaveLength(1); // First-wins: the community entity was seen first @@ -292,13 +306,19 @@ describe('BaseEntityProvider source metadata annotations', () => { metadata: { name: 'shared-name', namespace: 'community' }, }); - const entities = provider.getEntities([ - createFileData('/extensions/catalog-entities/plugin.yaml', primaryEntity), - createFileData( - '/extensions/extra/community/catalog-entities/plugin.yaml', - communityEntity, - ), - ]); + const entities = provider.getEntities( + [ + createFileData( + '/extensions/catalog-entities/plugin.yaml', + primaryEntity, + ), + createFileData( + '/extensions/extra/community/catalog-entities/plugin.yaml', + communityEntity, + ), + ], + root, + ); expect(entities).toHaveLength(2); const sources = entities.map( diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts index 99a6b2c85d9..03a34ab7d31 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts @@ -74,18 +74,24 @@ export abstract class BaseEntityProvider * * When `install-dynamic-plugins` extracts extra catalog index images * (via `EXTRA_CATALOG_INDEX_IMAGES`), entities are placed under - * `/extra//catalog-entities/…`. This method - * detects the `extra//` segment and returns `` as the + * `/extra//catalog-entities/…`. This method + * computes the relative path from `extensionsRoot` and checks whether + * the first segment is `extra/` — if so, the second segment is the * source identifier. All other paths return `"primary"`. */ - static deriveCatalogSource(filePath: string): string { - // Normalise to forward slashes so the regex works on all platforms. - const normalised = filePath.replace(/\\/g, '/'); - const match = normalised.match(/\/extra\/([^/]+)\//); - return match ? match[1] : BaseEntityProvider.DEFAULT_CATALOG_SOURCE; + static deriveCatalogSource(filePath: string, extensionsRoot: string): string { + const relative = path.relative(extensionsRoot, filePath); + const [first, second] = relative.split(path.sep); + return first === 'extra' && second + ? second + : BaseEntityProvider.DEFAULT_CATALOG_SOURCE; } - private addProviderAnnotations(entity: T, filePath: string): T { + private addProviderAnnotations( + entity: T, + filePath: string, + extensionsRoot: string, + ): T { return { ...entity, metadata: { @@ -95,13 +101,16 @@ export abstract class BaseEntityProvider [ANNOTATION_LOCATION]: `file:${this.getProviderName()}`, [ANNOTATION_ORIGIN_LOCATION]: `file:${this.getProviderName()}`, [ExtensionsAnnotation.CATALOG_SOURCE]: - BaseEntityProvider.deriveCatalogSource(filePath), + BaseEntityProvider.deriveCatalogSource(filePath, extensionsRoot), }, }, }; } - getEntities(allEntities: JsonFileData[]): T[] { + getEntities( + allEntities: JsonFileData[], + extensionsRoot: string = '', + ): T[] { if (allEntities.length === 0) { return []; } @@ -143,7 +152,8 @@ export abstract class BaseEntityProvider } return Array.from(entitiesByEntityRef.values()).map( - ({ entity, filePath }) => this.addProviderAnnotations(entity, filePath), + ({ entity, filePath }) => + this.addProviderAnnotations(entity, filePath, extensionsRoot), ); } @@ -239,7 +249,7 @@ export abstract class BaseEntityProvider } } - const entities: T[] = this.getEntities(yamlData); + const entities: T[] = this.getEntities(yamlData, extensionsFilePath ?? ''); await this.connection.applyMutation({ type: 'full', From 232abdd1fe601592f6147c4a4cd8b095e4a0ad27 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Tue, 21 Jul 2026 11:49:25 -0400 Subject: [PATCH 06/13] chore: update api reports Signed-off-by: Hope Hadfield --- .../plugins/catalog-backend-module-extensions/report.api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md b/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md index a5cbabe5877..4a4d59a19db 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md @@ -35,9 +35,9 @@ export abstract class BaseEntityProvider // (undocumented) connect(connection: EntityProviderConnection): Promise; static readonly DEFAULT_CATALOG_SOURCE = 'primary'; - static deriveCatalogSource(filePath: string): string; + static deriveCatalogSource(filePath: string, extensionsRoot: string): string; // (undocumented) - getEntities(allEntities: JsonFileData[]): T[]; + getEntities(allEntities: JsonFileData[], extensionsRoot?: string): T[]; // (undocumented) abstract getKind(): string; // (undocumented) From e50d61a961ad433f8772839fe4a7ffb20607b7d0 Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Thu, 23 Jul 2026 10:07:15 +0200 Subject: [PATCH 07/13] fix: add missing cli-defaults dependency to dismiss warning when testing extensions Signed-off-by: Christoph Jerolimov --- workspaces/extensions/package.json | 1 + workspaces/extensions/yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/workspaces/extensions/package.json b/workspaces/extensions/package.json index 914e2245cb5..5181b362931 100644 --- a/workspaces/extensions/package.json +++ b/workspaces/extensions/package.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@backstage/cli": "^0.36.3", + "@backstage/cli-defaults": "^0.1.3", "@backstage/e2e-test-utils": "^0.1.2", "@backstage/repo-tools": "^0.17.3", "@changesets/cli": "^2.27.1", diff --git a/workspaces/extensions/yarn.lock b/workspaces/extensions/yarn.lock index d42a2c7bf82..9528789ba01 100644 --- a/workspaces/extensions/yarn.lock +++ b/workspaces/extensions/yarn.lock @@ -6649,6 +6649,7 @@ __metadata: resolution: "@internal/extensions@workspace:." dependencies: "@backstage/cli": "npm:^0.36.3" + "@backstage/cli-defaults": "npm:^0.1.3" "@backstage/e2e-test-utils": "npm:^0.1.2" "@backstage/repo-tools": "npm:^0.17.3" "@changesets/cli": "npm:^2.27.1" From b48a672ab29c2e867a2c7bb0a7c42697b80e07e6 Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Thu, 23 Jul 2026 10:07:48 +0200 Subject: [PATCH 08/13] fix: remove certified test examples since this feature was removed Signed-off-by: Christoph Jerolimov --- workspaces/extensions/examples/all-plugins.yaml | 3 --- .../plugins/certified-plugin-1-by-vendor-a.yaml | 13 ------------- .../plugins/certified-plugin-2-by-vendor-a.yaml | 13 ------------- .../plugins/certified-plugin-3-by-vendor-b.yaml | 13 ------------- 4 files changed, 42 deletions(-) delete mode 100644 workspaces/extensions/examples/plugins/certified-plugin-1-by-vendor-a.yaml delete mode 100644 workspaces/extensions/examples/plugins/certified-plugin-2-by-vendor-a.yaml delete mode 100644 workspaces/extensions/examples/plugins/certified-plugin-3-by-vendor-b.yaml diff --git a/workspaces/extensions/examples/all-plugins.yaml b/workspaces/extensions/examples/all-plugins.yaml index c372d5e8eeb..06d501dc7a6 100644 --- a/workspaces/extensions/examples/all-plugins.yaml +++ b/workspaces/extensions/examples/all-plugins.yaml @@ -9,9 +9,6 @@ spec: - ./plugins/3scale.yaml - ./plugins/acr.yaml - ./plugins/bulk-import.yaml - - ./plugins/certified-plugin-1-by-vendor-a.yaml - - ./plugins/certified-plugin-2-by-vendor-a.yaml - - ./plugins/certified-plugin-3-by-vendor-b.yaml - ./plugins/keycloak.yaml - ./plugins/extensions.yaml - ./plugins/no-icon-no-description.yaml diff --git a/workspaces/extensions/examples/plugins/certified-plugin-1-by-vendor-a.yaml b/workspaces/extensions/examples/plugins/certified-plugin-1-by-vendor-a.yaml deleted file mode 100644 index 9e96bf4fad9..00000000000 --- a/workspaces/extensions/examples/plugins/certified-plugin-1-by-vendor-a.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# yaml-language-server: $schema=../../json-schema/plugins.json -apiVersion: extensions.backstage.io/v1alpha1 -kind: Plugin -metadata: - namespace: extensions-plugin-demo - name: certified-plugin-1-by-vendor-a - title: Certified Plugin 1 by Vendor A - description: This is a certified plugin example - annotations: - extensions.backstage.io/pre-installed: 'true' -spec: - categories: - - Test plugin entity diff --git a/workspaces/extensions/examples/plugins/certified-plugin-2-by-vendor-a.yaml b/workspaces/extensions/examples/plugins/certified-plugin-2-by-vendor-a.yaml deleted file mode 100644 index 44a34c360e1..00000000000 --- a/workspaces/extensions/examples/plugins/certified-plugin-2-by-vendor-a.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# yaml-language-server: $schema=../../json-schema/plugins.json -apiVersion: extensions.backstage.io/v1alpha1 -kind: Plugin -metadata: - namespace: extensions-plugin-demo - name: certified-plugin-2-by-vendor-a - title: Certified Plugin 2 by Vendor A - description: This is a certified plugin example - annotations: - extensions.backstage.io/pre-installed: 'true' -spec: - categories: - - Test plugin entity diff --git a/workspaces/extensions/examples/plugins/certified-plugin-3-by-vendor-b.yaml b/workspaces/extensions/examples/plugins/certified-plugin-3-by-vendor-b.yaml deleted file mode 100644 index 2f9f2d55176..00000000000 --- a/workspaces/extensions/examples/plugins/certified-plugin-3-by-vendor-b.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# yaml-language-server: $schema=../../json-schema/plugins.json -apiVersion: extensions.backstage.io/v1alpha1 -kind: Plugin -metadata: - namespace: extensions-plugin-demo - name: certified-plugin-3-by-vendor-b - title: Certified Plugin 3 by Vendor B - description: This is a certified plugin example - annotations: - extensions.backstage.io/pre-installed: 'true' -spec: - categories: - - Test plugin entity From ed8d468ee48952a3d347501dd7c80c5cacf95340 Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Thu, 23 Jul 2026 10:13:04 +0200 Subject: [PATCH 09/13] fix: add guest user and remove dangerously configuirations Signed-off-by: Christoph Jerolimov --- workspaces/extensions/app-config.yaml | 28 ++++++++++++++----------- workspaces/extensions/examples/org.yaml | 19 +++++++++++++++++ 2 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 workspaces/extensions/examples/org.yaml diff --git a/workspaces/extensions/app-config.yaml b/workspaces/extensions/app-config.yaml index d1dd92f786c..91c0407d350 100644 --- a/workspaces/extensions/app-config.yaml +++ b/workspaces/extensions/app-config.yaml @@ -26,7 +26,6 @@ backend: # See https://backstage.io/docs/auth/service-to-service-auth for # information on the format auth: - dangerouslyDisableDefaultAuthPolicy: true # keys: # - secret: ${BACKEND_SECRET} baseUrl: http://localhost:7007 @@ -101,21 +100,27 @@ catalog: pullRequestBranchName: backstage-integration rules: - allow: - [ - Component, - System, - API, - Resource, - Plugin, - PluginCollection, - Package, - Location, - ] + - Component + - System + - API + - Resource + - Plugin + - PluginCollection + - Package + - Location + locations: - type: file target: ../../catalog-info.yaml rules: - allow: [Component] + # Guest user and group + - type: file + target: ../../examples/org.yaml + rules: + - allow: [User, Group] + + # Test catalog - type: file target: ../../examples/all-orgs.yaml rules: @@ -157,4 +162,3 @@ extensions: # enabled: true # saveToSingleFile: # file: ${EXTENSIONS_PLUGIN_CONFIG} -dangerouslyAllowSignInWithoutUserInCatalog: true diff --git a/workspaces/extensions/examples/org.yaml b/workspaces/extensions/examples/org.yaml new file mode 100644 index 00000000000..29c1140e412 --- /dev/null +++ b/workspaces/extensions/examples/org.yaml @@ -0,0 +1,19 @@ +--- +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user +apiVersion: backstage.io/v1alpha1 +kind: User +metadata: + namespace: development + name: guest +spec: + memberOf: [guests] +--- +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group +apiVersion: backstage.io/v1alpha1 +kind: Group +metadata: + namespace: development + name: guests +spec: + type: team + children: [] \ No newline at end of file From c165b0972de375d970a58df62d6588816f09da37 Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Thu, 23 Jul 2026 10:29:21 +0200 Subject: [PATCH 10/13] chore: move examples, and load them via catalog module so that the new catalog-source annotation gots automatically added Signed-off-by: Christoph Jerolimov --- workspaces/extensions/app-config.yaml | 24 +++------------ .../extensions/examples/all-collections.yaml | 10 ------- workspaces/extensions/examples/all-orgs.yaml | 9 ------ .../extensions/examples/all-packages.yaml | 18 ------------ .../extensions/examples/all-plugins.yaml | 29 ------------------- .../{plugins => extra/community}/3scale.yaml | 0 .../{plugins => extra/community}/acr.yaml | 0 ...stage-community-plugin-3scale-backend.yaml | 0 .../backstage-community-plugin-acr.yaml | 0 ...lugin-catalog-backend-module-keycloak.yaml | 0 .../backstage-community-plugin-quay.yaml | 0 .../backstage-community-plugin-tekton.yaml | 0 .../backstage-community-plugin-topology.yaml | 0 .../community}/keycloak.yaml | 0 .../{plugins => extra/community}/quay.yaml | 0 .../{plugins => extra/community}/tekton.yaml | 0 .../community}/topology.yaml | 0 .../redhat}/bulk-import.yaml | 0 .../{plugins => extra/redhat}/extensions.yaml | 0 ...-backstage-plugin-bulk-import-backend.yaml | 0 ...oper-hub-backstage-plugin-bulk-import.yaml | 0 ...b-backstage-plugin-extensions-backend.yaml | 0 ...loper-hub-backstage-plugin-extensions.yaml | 0 workspaces/extensions/examples/org.yaml | 11 ++++++- .../extensions/examples/orgs/redhat.yaml | 8 ----- .../no-icon-no-description.yaml | 0 .../pre-installed-false.yaml | 0 .../pre-installed-true.yaml | 0 .../pre-installed-undefined.yaml | 0 .../{plugins => other-tests}/search.yaml | 0 .../support-community.yaml | 0 .../support-dev-preview.yaml | 0 .../support-generally-available.yaml | 0 .../support-none.yaml | 0 .../support-string.yaml | 0 .../support-tech-preview.yaml | 0 .../support-undefined.yaml | 0 .../support-unknown-level.yaml | 0 38 files changed, 14 insertions(+), 95 deletions(-) delete mode 100644 workspaces/extensions/examples/all-collections.yaml delete mode 100644 workspaces/extensions/examples/all-orgs.yaml delete mode 100644 workspaces/extensions/examples/all-packages.yaml delete mode 100644 workspaces/extensions/examples/all-plugins.yaml rename workspaces/extensions/examples/{plugins => extra/community}/3scale.yaml (100%) rename workspaces/extensions/examples/{plugins => extra/community}/acr.yaml (100%) rename workspaces/extensions/examples/{packages => extra/community}/backstage-community-plugin-3scale-backend.yaml (100%) rename workspaces/extensions/examples/{packages => extra/community}/backstage-community-plugin-acr.yaml (100%) rename workspaces/extensions/examples/{packages => extra/community}/backstage-community-plugin-catalog-backend-module-keycloak.yaml (100%) rename workspaces/extensions/examples/{packages => extra/community}/backstage-community-plugin-quay.yaml (100%) rename workspaces/extensions/examples/{packages => extra/community}/backstage-community-plugin-tekton.yaml (100%) rename workspaces/extensions/examples/{packages => extra/community}/backstage-community-plugin-topology.yaml (100%) rename workspaces/extensions/examples/{plugins => extra/community}/keycloak.yaml (100%) rename workspaces/extensions/examples/{plugins => extra/community}/quay.yaml (100%) rename workspaces/extensions/examples/{plugins => extra/community}/tekton.yaml (100%) rename workspaces/extensions/examples/{plugins => extra/community}/topology.yaml (100%) rename workspaces/extensions/examples/{plugins => extra/redhat}/bulk-import.yaml (100%) rename workspaces/extensions/examples/{plugins => extra/redhat}/extensions.yaml (100%) rename workspaces/extensions/examples/{packages => extra/redhat}/red-hat-developer-hub-backstage-plugin-bulk-import-backend.yaml (100%) rename workspaces/extensions/examples/{packages => extra/redhat}/red-hat-developer-hub-backstage-plugin-bulk-import.yaml (100%) rename workspaces/extensions/examples/{packages => extra/redhat}/red-hat-developer-hub-backstage-plugin-extensions-backend.yaml (100%) rename workspaces/extensions/examples/{packages => extra/redhat}/red-hat-developer-hub-backstage-plugin-extensions.yaml (100%) delete mode 100644 workspaces/extensions/examples/orgs/redhat.yaml rename workspaces/extensions/examples/{plugins => other-tests}/no-icon-no-description.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/pre-installed-false.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/pre-installed-true.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/pre-installed-undefined.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/search.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/support-community.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/support-dev-preview.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/support-generally-available.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/support-none.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/support-string.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/support-tech-preview.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/support-undefined.yaml (100%) rename workspaces/extensions/examples/{plugins => other-tests}/support-unknown-level.yaml (100%) diff --git a/workspaces/extensions/app-config.yaml b/workspaces/extensions/app-config.yaml index 91c0407d350..a8b460c2ec1 100644 --- a/workspaces/extensions/app-config.yaml +++ b/workspaces/extensions/app-config.yaml @@ -120,24 +120,6 @@ catalog: rules: - allow: [User, Group] - # Test catalog - - type: file - target: ../../examples/all-orgs.yaml - rules: - - allow: [Group] - - type: file - target: ../../examples/all-plugins.yaml - rules: - - allow: [Plugin] - - type: file - target: ../../examples/all-collections.yaml - rules: - - allow: [PluginCollection] - - type: file - target: ../../examples/all-packages.yaml - rules: - - allow: [Package] - kubernetes: # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options @@ -156,8 +138,10 @@ permission: - name: user:development/guest extensions: - # directory: ../../examples # YAML files are in the examples directory - ### Example for how to enable installation to a file. + # YAML files are in the examples directory + directory: ../../examples + + # Example for how to enable installation to a file. # installation: # enabled: true # saveToSingleFile: diff --git a/workspaces/extensions/examples/all-collections.yaml b/workspaces/extensions/examples/all-collections.yaml deleted file mode 100644 index 18b52b26cd7..00000000000 --- a/workspaces/extensions/examples/all-collections.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: backstage.io/v1alpha1 -kind: Location -metadata: - namespace: extensions-plugin-demo - name: all-collections - description: A collection of extensions plugin collections -spec: - targets: - - ./collections/featured.yaml - - ./collections/kubernetes.yaml diff --git a/workspaces/extensions/examples/all-orgs.yaml b/workspaces/extensions/examples/all-orgs.yaml deleted file mode 100644 index 196142b6a35..00000000000 --- a/workspaces/extensions/examples/all-orgs.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: backstage.io/v1alpha1 -kind: Location -metadata: - namespace: extensions-plugin-demo - name: all-orgs - description: A collection of all the extensions orgs -spec: - targets: - - ./orgs/redhat.yaml diff --git a/workspaces/extensions/examples/all-packages.yaml b/workspaces/extensions/examples/all-packages.yaml deleted file mode 100644 index 4eacc4fc143..00000000000 --- a/workspaces/extensions/examples/all-packages.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: backstage.io/v1alpha1 -kind: Location -metadata: - namespace: extensions-plugin-demo - name: all-packages - description: A collection of all the extensions packages -spec: - targets: - - ./packages/backstage-community-plugin-3scale-backend.yaml - - ./packages/backstage-community-plugin-acr.yaml - - ./packages/backstage-community-plugin-catalog-backend-module-keycloak.yaml - - ./packages/backstage-community-plugin-quay.yaml - - ./packages/backstage-community-plugin-tekton.yaml - - ./packages/backstage-community-plugin-topology.yaml - - ./packages/red-hat-developer-hub-backstage-plugin-bulk-import-backend.yaml - - ./packages/red-hat-developer-hub-backstage-plugin-bulk-import.yaml - - ./packages/red-hat-developer-hub-backstage-plugin-extensions-backend.yaml - - ./packages/red-hat-developer-hub-backstage-plugin-extensions.yaml diff --git a/workspaces/extensions/examples/all-plugins.yaml b/workspaces/extensions/examples/all-plugins.yaml deleted file mode 100644 index 06d501dc7a6..00000000000 --- a/workspaces/extensions/examples/all-plugins.yaml +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: backstage.io/v1alpha1 -kind: Location -metadata: - namespace: extensions-plugin-demo - name: all-plugins - description: A collection of all the extensions plugins -spec: - targets: - - ./plugins/3scale.yaml - - ./plugins/acr.yaml - - ./plugins/bulk-import.yaml - - ./plugins/keycloak.yaml - - ./plugins/extensions.yaml - - ./plugins/no-icon-no-description.yaml - - ./plugins/pre-installed-false.yaml - - ./plugins/pre-installed-true.yaml - - ./plugins/pre-installed-undefined.yaml - - ./plugins/quay.yaml - - ./plugins/search.yaml - - ./plugins/support-community.yaml - - ./plugins/support-dev-preview.yaml - - ./plugins/support-generally-available.yaml - - ./plugins/support-none.yaml - - ./plugins/support-string.yaml - - ./plugins/support-tech-preview.yaml - - ./plugins/support-undefined.yaml - - ./plugins/support-unknown-level.yaml - - ./plugins/tekton.yaml - - ./plugins/topology.yaml diff --git a/workspaces/extensions/examples/plugins/3scale.yaml b/workspaces/extensions/examples/extra/community/3scale.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/3scale.yaml rename to workspaces/extensions/examples/extra/community/3scale.yaml diff --git a/workspaces/extensions/examples/plugins/acr.yaml b/workspaces/extensions/examples/extra/community/acr.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/acr.yaml rename to workspaces/extensions/examples/extra/community/acr.yaml diff --git a/workspaces/extensions/examples/packages/backstage-community-plugin-3scale-backend.yaml b/workspaces/extensions/examples/extra/community/backstage-community-plugin-3scale-backend.yaml similarity index 100% rename from workspaces/extensions/examples/packages/backstage-community-plugin-3scale-backend.yaml rename to workspaces/extensions/examples/extra/community/backstage-community-plugin-3scale-backend.yaml diff --git a/workspaces/extensions/examples/packages/backstage-community-plugin-acr.yaml b/workspaces/extensions/examples/extra/community/backstage-community-plugin-acr.yaml similarity index 100% rename from workspaces/extensions/examples/packages/backstage-community-plugin-acr.yaml rename to workspaces/extensions/examples/extra/community/backstage-community-plugin-acr.yaml diff --git a/workspaces/extensions/examples/packages/backstage-community-plugin-catalog-backend-module-keycloak.yaml b/workspaces/extensions/examples/extra/community/backstage-community-plugin-catalog-backend-module-keycloak.yaml similarity index 100% rename from workspaces/extensions/examples/packages/backstage-community-plugin-catalog-backend-module-keycloak.yaml rename to workspaces/extensions/examples/extra/community/backstage-community-plugin-catalog-backend-module-keycloak.yaml diff --git a/workspaces/extensions/examples/packages/backstage-community-plugin-quay.yaml b/workspaces/extensions/examples/extra/community/backstage-community-plugin-quay.yaml similarity index 100% rename from workspaces/extensions/examples/packages/backstage-community-plugin-quay.yaml rename to workspaces/extensions/examples/extra/community/backstage-community-plugin-quay.yaml diff --git a/workspaces/extensions/examples/packages/backstage-community-plugin-tekton.yaml b/workspaces/extensions/examples/extra/community/backstage-community-plugin-tekton.yaml similarity index 100% rename from workspaces/extensions/examples/packages/backstage-community-plugin-tekton.yaml rename to workspaces/extensions/examples/extra/community/backstage-community-plugin-tekton.yaml diff --git a/workspaces/extensions/examples/packages/backstage-community-plugin-topology.yaml b/workspaces/extensions/examples/extra/community/backstage-community-plugin-topology.yaml similarity index 100% rename from workspaces/extensions/examples/packages/backstage-community-plugin-topology.yaml rename to workspaces/extensions/examples/extra/community/backstage-community-plugin-topology.yaml diff --git a/workspaces/extensions/examples/plugins/keycloak.yaml b/workspaces/extensions/examples/extra/community/keycloak.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/keycloak.yaml rename to workspaces/extensions/examples/extra/community/keycloak.yaml diff --git a/workspaces/extensions/examples/plugins/quay.yaml b/workspaces/extensions/examples/extra/community/quay.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/quay.yaml rename to workspaces/extensions/examples/extra/community/quay.yaml diff --git a/workspaces/extensions/examples/plugins/tekton.yaml b/workspaces/extensions/examples/extra/community/tekton.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/tekton.yaml rename to workspaces/extensions/examples/extra/community/tekton.yaml diff --git a/workspaces/extensions/examples/plugins/topology.yaml b/workspaces/extensions/examples/extra/community/topology.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/topology.yaml rename to workspaces/extensions/examples/extra/community/topology.yaml diff --git a/workspaces/extensions/examples/plugins/bulk-import.yaml b/workspaces/extensions/examples/extra/redhat/bulk-import.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/bulk-import.yaml rename to workspaces/extensions/examples/extra/redhat/bulk-import.yaml diff --git a/workspaces/extensions/examples/plugins/extensions.yaml b/workspaces/extensions/examples/extra/redhat/extensions.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/extensions.yaml rename to workspaces/extensions/examples/extra/redhat/extensions.yaml diff --git a/workspaces/extensions/examples/packages/red-hat-developer-hub-backstage-plugin-bulk-import-backend.yaml b/workspaces/extensions/examples/extra/redhat/red-hat-developer-hub-backstage-plugin-bulk-import-backend.yaml similarity index 100% rename from workspaces/extensions/examples/packages/red-hat-developer-hub-backstage-plugin-bulk-import-backend.yaml rename to workspaces/extensions/examples/extra/redhat/red-hat-developer-hub-backstage-plugin-bulk-import-backend.yaml diff --git a/workspaces/extensions/examples/packages/red-hat-developer-hub-backstage-plugin-bulk-import.yaml b/workspaces/extensions/examples/extra/redhat/red-hat-developer-hub-backstage-plugin-bulk-import.yaml similarity index 100% rename from workspaces/extensions/examples/packages/red-hat-developer-hub-backstage-plugin-bulk-import.yaml rename to workspaces/extensions/examples/extra/redhat/red-hat-developer-hub-backstage-plugin-bulk-import.yaml diff --git a/workspaces/extensions/examples/packages/red-hat-developer-hub-backstage-plugin-extensions-backend.yaml b/workspaces/extensions/examples/extra/redhat/red-hat-developer-hub-backstage-plugin-extensions-backend.yaml similarity index 100% rename from workspaces/extensions/examples/packages/red-hat-developer-hub-backstage-plugin-extensions-backend.yaml rename to workspaces/extensions/examples/extra/redhat/red-hat-developer-hub-backstage-plugin-extensions-backend.yaml diff --git a/workspaces/extensions/examples/packages/red-hat-developer-hub-backstage-plugin-extensions.yaml b/workspaces/extensions/examples/extra/redhat/red-hat-developer-hub-backstage-plugin-extensions.yaml similarity index 100% rename from workspaces/extensions/examples/packages/red-hat-developer-hub-backstage-plugin-extensions.yaml rename to workspaces/extensions/examples/extra/redhat/red-hat-developer-hub-backstage-plugin-extensions.yaml diff --git a/workspaces/extensions/examples/org.yaml b/workspaces/extensions/examples/org.yaml index 29c1140e412..c3d572f10a9 100644 --- a/workspaces/extensions/examples/org.yaml +++ b/workspaces/extensions/examples/org.yaml @@ -16,4 +16,13 @@ metadata: name: guests spec: type: team - children: [] \ No newline at end of file + children: [] +--- +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group +apiVersion: backstage.io/v1alpha1 +kind: Group +metadata: + name: redhat +spec: + type: team + children: [] diff --git a/workspaces/extensions/examples/orgs/redhat.yaml b/workspaces/extensions/examples/orgs/redhat.yaml deleted file mode 100644 index bab7d4aa038..00000000000 --- a/workspaces/extensions/examples/orgs/redhat.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group -apiVersion: backstage.io/v1alpha1 -kind: Group -metadata: - name: redhat -spec: - type: team - children: [] diff --git a/workspaces/extensions/examples/plugins/no-icon-no-description.yaml b/workspaces/extensions/examples/other-tests/no-icon-no-description.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/no-icon-no-description.yaml rename to workspaces/extensions/examples/other-tests/no-icon-no-description.yaml diff --git a/workspaces/extensions/examples/plugins/pre-installed-false.yaml b/workspaces/extensions/examples/other-tests/pre-installed-false.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/pre-installed-false.yaml rename to workspaces/extensions/examples/other-tests/pre-installed-false.yaml diff --git a/workspaces/extensions/examples/plugins/pre-installed-true.yaml b/workspaces/extensions/examples/other-tests/pre-installed-true.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/pre-installed-true.yaml rename to workspaces/extensions/examples/other-tests/pre-installed-true.yaml diff --git a/workspaces/extensions/examples/plugins/pre-installed-undefined.yaml b/workspaces/extensions/examples/other-tests/pre-installed-undefined.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/pre-installed-undefined.yaml rename to workspaces/extensions/examples/other-tests/pre-installed-undefined.yaml diff --git a/workspaces/extensions/examples/plugins/search.yaml b/workspaces/extensions/examples/other-tests/search.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/search.yaml rename to workspaces/extensions/examples/other-tests/search.yaml diff --git a/workspaces/extensions/examples/plugins/support-community.yaml b/workspaces/extensions/examples/other-tests/support-community.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/support-community.yaml rename to workspaces/extensions/examples/other-tests/support-community.yaml diff --git a/workspaces/extensions/examples/plugins/support-dev-preview.yaml b/workspaces/extensions/examples/other-tests/support-dev-preview.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/support-dev-preview.yaml rename to workspaces/extensions/examples/other-tests/support-dev-preview.yaml diff --git a/workspaces/extensions/examples/plugins/support-generally-available.yaml b/workspaces/extensions/examples/other-tests/support-generally-available.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/support-generally-available.yaml rename to workspaces/extensions/examples/other-tests/support-generally-available.yaml diff --git a/workspaces/extensions/examples/plugins/support-none.yaml b/workspaces/extensions/examples/other-tests/support-none.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/support-none.yaml rename to workspaces/extensions/examples/other-tests/support-none.yaml diff --git a/workspaces/extensions/examples/plugins/support-string.yaml b/workspaces/extensions/examples/other-tests/support-string.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/support-string.yaml rename to workspaces/extensions/examples/other-tests/support-string.yaml diff --git a/workspaces/extensions/examples/plugins/support-tech-preview.yaml b/workspaces/extensions/examples/other-tests/support-tech-preview.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/support-tech-preview.yaml rename to workspaces/extensions/examples/other-tests/support-tech-preview.yaml diff --git a/workspaces/extensions/examples/plugins/support-undefined.yaml b/workspaces/extensions/examples/other-tests/support-undefined.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/support-undefined.yaml rename to workspaces/extensions/examples/other-tests/support-undefined.yaml diff --git a/workspaces/extensions/examples/plugins/support-unknown-level.yaml b/workspaces/extensions/examples/other-tests/support-unknown-level.yaml similarity index 100% rename from workspaces/extensions/examples/plugins/support-unknown-level.yaml rename to workspaces/extensions/examples/other-tests/support-unknown-level.yaml From 49a903a271c0b0c380b96a231f9e9f620d8fefeb Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Thu, 23 Jul 2026 20:23:10 +0200 Subject: [PATCH 11/13] fix(extensions): add a log message whe plugins get imported and wait in playwright on that message Signed-off-by: Christoph Jerolimov --- workspaces/extensions/playwright.config.ts | 6 +++++- .../plugins/catalog-backend-module-extensions/src/module.ts | 4 ++-- .../src/providers/BaseEntityProvider.ts | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/workspaces/extensions/playwright.config.ts b/workspaces/extensions/playwright.config.ts index 1a9f696e3d9..83de61e0a39 100644 --- a/workspaces/extensions/playwright.config.ts +++ b/workspaces/extensions/playwright.config.ts @@ -37,9 +37,13 @@ export default defineConfig({ : [ { command: startCommand, - port: 3000, reuseExistingServer: false, cwd: __dirname, + wait: { + // wait for this message and expect 10+ loaded plugins. + stdout: + /extensions-plugin-provider applied [1-9]\d+ entities to the catalog/, + }, }, ], diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/module.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/module.ts index 0a5a1b01e84..d9bd89bd2d6 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/module.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/module.ts @@ -61,11 +61,11 @@ export const catalogModuleExtensions = createBackendModule({ }) { logger.info('Adding Extensions providers and processors to catalog...'); const taskRunner = scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, + frequency: { hours: 1 }, timeout: { minutes: 10 }, }); const delayedTaskRunner = scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, + frequency: { hours: 1 }, timeout: { minutes: 10 }, initialDelay: { seconds: 20 }, }); diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts index 03a34ab7d31..06adbd73bbb 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts @@ -258,5 +258,9 @@ export abstract class BaseEntityProvider locationKey: `file:${this.getProviderName()}`, })), }); + + this.logger?.info( + `${this.getProviderName()} applied ${entities.length} entities to the catalog.`, + ); } } From 45748cee7a255e019dca4f99211990f096a9f7a7 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Thu, 23 Jul 2026 16:53:53 -0400 Subject: [PATCH 12/13] fix: move org.yaml out of extensions directory to avoid multi-doc YAML parse error Signed-off-by: Hope Hadfield --- workspaces/extensions/app-config.yaml | 2 +- workspaces/extensions/{examples => }/org.yaml | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename workspaces/extensions/{examples => }/org.yaml (100%) diff --git a/workspaces/extensions/app-config.yaml b/workspaces/extensions/app-config.yaml index a8b460c2ec1..88e8941a13b 100644 --- a/workspaces/extensions/app-config.yaml +++ b/workspaces/extensions/app-config.yaml @@ -116,7 +116,7 @@ catalog: - allow: [Component] # Guest user and group - type: file - target: ../../examples/org.yaml + target: ../../org.yaml rules: - allow: [User, Group] diff --git a/workspaces/extensions/examples/org.yaml b/workspaces/extensions/org.yaml similarity index 100% rename from workspaces/extensions/examples/org.yaml rename to workspaces/extensions/org.yaml From 0a5895cbd77f6b9abf3a9d7cea8b45a717a5f0de Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Fri, 24 Jul 2026 16:29:01 -0400 Subject: [PATCH 13/13] fix: update e2e test to expect Tekton for CI/CD + Red Hat filter Signed-off-by: Hope Hadfield --- workspaces/extensions/e2e-tests/extensions.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/extensions/e2e-tests/extensions.test.ts b/workspaces/extensions/e2e-tests/extensions.test.ts index 8924c9bb901..d464376f4e2 100644 --- a/workspaces/extensions/e2e-tests/extensions.test.ts +++ b/workspaces/extensions/e2e-tests/extensions.test.ts @@ -108,14 +108,14 @@ test.describe('Admin > Extensions', () => { await extensions.selectDropdown(translations.search.author); await extensions.toggleOption('Red Hat'); await sharedPage.keyboard.press(`Escape`); - await extensionHelper.verifyHeading('APIs with 3scale'); + await extensionHelper.verifyHeading('Pipelines with Tekton'); await extensionHelper.verifyTextInLocator( '', `${translations.metadata.by} Red Hat`, true, ); - await extensionHelper.clickHeading('APIs with 3scale'); + await extensionHelper.clickHeading('Pipelines with Tekton'); await extensionHelper.verifyTableHeadingAndRows([ translations.table.packageName, translations.table.version,