Skip to content

Commit fab1471

Browse files
feat(marketplace): Add marketplace providers to add entities into the catalog (#483)
* add marketplace providers to ingest the entities into catalog * removed commented code * unregister collection provider in 1.5
1 parent 0fa1d3a commit fab1471

16 files changed

+582
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-catalog-backend-module-marketplace': patch
3+
---
4+
5+
Add marketplace providers to add entities into catalog

workspaces/marketplace/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"start-backend": "yarn workspace backend start",
1212
"build:backend": "yarn workspace backend build",
1313
"tsc": "tsc",
14-
"tsc:full": "tsc --skipLibCheck false --incremental false",
14+
"tsc:full": "tsc --skipLibCheck true --incremental false",
1515
"build:all": "backstage-cli repo build --all",
1616
"build:api-reports": "yarn build:api-reports:only --tsc",
1717
"build:api-reports:only": "backstage-repo-tools api-reports -o ae-wrong-input-file-type,ae-undocumented --validate-release-tags",

workspaces/marketplace/plugins/catalog-backend-module-marketplace/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,15 @@
3939
"@backstage/plugin-catalog-node": "^1.15.1",
4040
"@backstage/types": "^1.2.1",
4141
"@red-hat-developer-hub/backstage-plugin-marketplace-common": "workspace:^",
42+
"find-root": "^1.1.0",
43+
"glob": "^8.1.0",
44+
"js-yaml": "^4.1.0",
4245
"semver": "^7.6.3"
4346
},
4447
"devDependencies": {
4548
"@backstage/backend-test-utils": "^1.2.1",
46-
"@backstage/cli": "^0.29.5"
49+
"@backstage/cli": "^0.29.5",
50+
"@types/glob": "^8.1.0"
4751
},
4852
"files": [
4953
"dist"

workspaces/marketplace/plugins/catalog-backend-module-marketplace/report.api.md

+49
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,28 @@ import { CatalogProcessorCache } from '@backstage/plugin-catalog-node';
1111
import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node';
1212
import { DiscoveryService } from '@backstage/backend-plugin-api';
1313
import { Entity } from '@backstage/catalog-model';
14+
import { EntityProvider } from '@backstage/plugin-catalog-node';
15+
import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
1416
import { LocationSpec } from '@backstage/plugin-catalog-common';
17+
import { MarketplaceCollection } from '@red-hat-developer-hub/backstage-plugin-marketplace-common';
1518
import { MarketplacePackage } from '@red-hat-developer-hub/backstage-plugin-marketplace-common';
1619
import { MarketplacePlugin } from '@red-hat-developer-hub/backstage-plugin-marketplace-common';
20+
import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api';
21+
22+
// @public (undocumented)
23+
export abstract class BaseEntityProvider<T extends Entity> implements EntityProvider {
24+
constructor(taskRunner: SchedulerServiceTaskRunner);
25+
// (undocumented)
26+
connect(connection: EntityProviderConnection): Promise<void>;
27+
// (undocumented)
28+
getEntities(allEntities: JsonFileData<T>[]): T[];
29+
// (undocumented)
30+
abstract getKind(): string;
31+
// (undocumented)
32+
abstract getProviderName(): string;
33+
// (undocumented)
34+
run(): Promise<void>;
35+
}
1736

1837
// @public (undocumented)
1938
export type CachedData = {
@@ -39,6 +58,12 @@ export class DynamicPackageInstallStatusProcessor implements CatalogProcessor {
3958
preProcessEntity(entity: Entity, _location: LocationSpec, _emit: CatalogProcessorEmit, _originLocation: LocationSpec, cache: CatalogProcessorCache): Promise<Entity>;
4059
}
4160

61+
// @public (undocumented)
62+
export type JsonFileData<T> = {
63+
filePath: string;
64+
content: T;
65+
};
66+
4267
// @public (undocumented)
4368
export class LocalPackageInstallStatusProcessor implements CatalogProcessor {
4469
constructor(paths?: string[]);
@@ -60,6 +85,14 @@ export class MarketplaceCollectionProcessor implements CatalogProcessor {
6085
validateEntityKind(entity: Entity): Promise<boolean>;
6186
}
6287

88+
// @public (undocumented)
89+
export class MarketplaceCollectionProvider extends BaseEntityProvider<MarketplaceCollection> {
90+
// (undocumented)
91+
getKind(): string;
92+
// (undocumented)
93+
getProviderName(): string;
94+
}
95+
6396
// @public (undocumented)
6497
export class MarketplacePackageProcessor implements CatalogProcessor {
6598
// (undocumented)
@@ -70,6 +103,14 @@ export class MarketplacePackageProcessor implements CatalogProcessor {
70103
validateEntityKind(entity: Entity): Promise<boolean>;
71104
}
72105

106+
// @public (undocumented)
107+
export class MarketplacePackageProvider extends BaseEntityProvider<MarketplacePackage> {
108+
// (undocumented)
109+
getKind(): string;
110+
// (undocumented)
111+
getProviderName(): string;
112+
}
113+
73114
// @public (undocumented)
74115
export class MarketplacePluginProcessor implements CatalogProcessor {
75116
// (undocumented)
@@ -80,4 +121,12 @@ export class MarketplacePluginProcessor implements CatalogProcessor {
80121
validateEntityKind(entity: Entity): Promise<boolean>;
81122
}
82123

124+
// @public (undocumented)
125+
export class MarketplacePluginProvider extends BaseEntityProvider<MarketplacePlugin> {
126+
// (undocumented)
127+
getKind(): string;
128+
// (undocumented)
129+
getProviderName(): string;
130+
}
131+
83132
```

workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@
2323
export { catalogModuleMarketplace as default } from './module';
2424

2525
export * from './processors';
26+
export * from './providers';
27+
export * from './types';

workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/module.test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import { startTestBackend } from '@backstage/backend-test-utils';
2020

2121
describe('catalogModuleMarketplace', () => {
2222
it('should register the extension point', async () => {
23-
const extensionPoint = { addProcessor: jest.fn() };
23+
const extensionPoint = {
24+
addProcessor: jest.fn(),
25+
addEntityProvider: jest.fn(),
26+
};
2427
await startTestBackend({
2528
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
2629
features: [catalogModuleMarketplace],

workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/module.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import { MarketplaceCollectionProcessor } from './processors/MarketplaceCollecti
2525
import { DynamicPackageInstallStatusProcessor } from './processors/DynamicPackageInstallStatusProcessor';
2626
import { LocalPackageInstallStatusProcessor } from './processors/LocalPackageInstallStatusProcessor';
2727
import { MarketplacePackageProcessor } from './processors/MarketplacePackageProcessor';
28+
import { MarketplacePluginProvider } from './providers/MarketplacePluginProvider';
29+
import { MarketplacePackageProvider } from './providers/MarketplacePackageProvider';
2830

2931
/**
3032
* @public
@@ -39,9 +41,23 @@ export const catalogModuleMarketplace = createBackendModule({
3941
catalog: catalogProcessingExtensionPoint,
4042
discovery: coreServices.discovery,
4143
auth: coreServices.auth,
44+
scheduler: coreServices.scheduler,
4245
},
43-
async init({ logger, catalog, discovery, auth }) {
44-
logger.info('Adding Marketplace processors to catalog...');
46+
async init({ logger, catalog, discovery, auth, scheduler }) {
47+
logger.info(
48+
'Adding Marketplace providers and processors to catalog...',
49+
);
50+
const taskRunner = scheduler.createScheduledTaskRunner({
51+
frequency: { minutes: 30 },
52+
timeout: { minutes: 10 },
53+
});
54+
55+
catalog.addEntityProvider(new MarketplacePackageProvider(taskRunner));
56+
catalog.addEntityProvider(new MarketplacePluginProvider(taskRunner));
57+
// Disabling the collection provider as collections/all.yaml is already commented in RHDH 1.5 image.
58+
// catalog.addEntityProvider(
59+
// new MarketplaceCollectionProvider(taskRunner),
60+
// );
4561
catalog.addProcessor(new MarketplacePluginProcessor());
4662
catalog.addProcessor(new MarketplaceCollectionProcessor());
4763
catalog.addProcessor(new LocalPackageInstallStatusProcessor());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api';
17+
import {
18+
ANNOTATION_LOCATION,
19+
ANNOTATION_ORIGIN_LOCATION,
20+
Entity,
21+
} from '@backstage/catalog-model';
22+
import {
23+
EntityProvider,
24+
EntityProviderConnection,
25+
} from '@backstage/plugin-catalog-node';
26+
27+
import { findTopmostFolder, readYamlFiles } from '../utils/file-utils';
28+
import { JsonFileData } from '../types';
29+
30+
/**
31+
* @public
32+
*/
33+
export abstract class BaseEntityProvider<T extends Entity>
34+
implements EntityProvider
35+
{
36+
private connection?: EntityProviderConnection;
37+
private taskRunner: SchedulerServiceTaskRunner;
38+
39+
constructor(taskRunner: SchedulerServiceTaskRunner) {
40+
this.taskRunner = taskRunner;
41+
}
42+
43+
abstract getProviderName(): string;
44+
abstract getKind(): string;
45+
46+
getEntities(allEntities: JsonFileData<T>[]): T[] {
47+
if (allEntities.length === 0) {
48+
return [];
49+
}
50+
return allEntities
51+
.filter(d => d.content.kind === this.getKind())
52+
.map(file => ({
53+
...file.content,
54+
metadata: {
55+
...file.content.metadata,
56+
annotations: {
57+
[ANNOTATION_LOCATION]: `file:${this.getProviderName()}`,
58+
[ANNOTATION_ORIGIN_LOCATION]: `file:${this.getProviderName()}`,
59+
},
60+
},
61+
}));
62+
}
63+
64+
async connect(connection: EntityProviderConnection): Promise<void> {
65+
this.connection = connection;
66+
await this.taskRunner.run({
67+
id: this.getProviderName(),
68+
fn: async () => {
69+
await this.run();
70+
},
71+
});
72+
}
73+
74+
async run(): Promise<void> {
75+
if (!this.connection) {
76+
throw new Error('Not initialized');
77+
}
78+
79+
const marketplaceFilePath = findTopmostFolder('marketplace');
80+
81+
let yamlData: JsonFileData<T>[] = [];
82+
if (marketplaceFilePath) {
83+
try {
84+
yamlData = readYamlFiles(marketplaceFilePath);
85+
} catch (error) {
86+
console.error(error.message);
87+
}
88+
}
89+
90+
const entities: T[] = this.getEntities(yamlData);
91+
92+
await this.connection.applyMutation({
93+
type: 'full',
94+
entities: entities.map(entity => ({
95+
entity,
96+
locationKey: `file:${this.getProviderName()}`,
97+
})),
98+
});
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import {
17+
MarketplaceCollection,
18+
MarketplaceKind,
19+
} from '@red-hat-developer-hub/backstage-plugin-marketplace-common';
20+
import { BaseEntityProvider } from './BaseEntityProvider';
21+
22+
/**
23+
* @public
24+
*/
25+
export class MarketplaceCollectionProvider extends BaseEntityProvider<MarketplaceCollection> {
26+
getKind(): string {
27+
return MarketplaceKind.Collection;
28+
}
29+
30+
getProviderName(): string {
31+
return 'marketplace-collection-provider';
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import {
17+
MarketplaceKind,
18+
MarketplacePackage,
19+
} from '@red-hat-developer-hub/backstage-plugin-marketplace-common';
20+
import { BaseEntityProvider } from './BaseEntityProvider';
21+
22+
/**
23+
* @public
24+
*/
25+
export class MarketplacePackageProvider extends BaseEntityProvider<MarketplacePackage> {
26+
getKind(): string {
27+
return MarketplaceKind.Package;
28+
}
29+
30+
getProviderName(): string {
31+
return 'marketplace-package-provider';
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import {
17+
MarketplaceKind,
18+
MarketplacePlugin,
19+
} from '@red-hat-developer-hub/backstage-plugin-marketplace-common';
20+
import { BaseEntityProvider } from './BaseEntityProvider';
21+
22+
/**
23+
* @public
24+
*/
25+
export class MarketplacePluginProvider extends BaseEntityProvider<MarketplacePlugin> {
26+
getKind(): string {
27+
return MarketplaceKind.Plugin;
28+
}
29+
30+
getProviderName(): string {
31+
return 'marketplace-plugin-provider';
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
export * from './BaseEntityProvider';
17+
export * from './MarketplacePluginProvider';
18+
export * from './MarketplaceCollectionProvider';
19+
export * from './MarketplacePackageProvider';

0 commit comments

Comments
 (0)