Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions plugins/incremental-ingestion-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ This approach has the following benefits,
```ts
const builder = CatalogBuilder.create(env);
// incremental builder receives builder because it'll register
// incremental entity providers with the builder
// incremental entity providers with the builder
const incrementalBuilder = IncrementalCatalogBuilder.create(env, builder);
```
3. Last step, add `await incrementBuilder.build()` after `await builder.build()` to ensure that all `CatalogBuider` migration run before running `incrementBuilder.build()` migrations.
3. Last step, add `await incrementalBuilder.build()` after `await builder.build()` to ensure that all `CatalogBuider` migration run before running `incrementalBuilder.build()` migrations.
```ts
const { processingEngine, router } = await builder.build();

// this has to run after `await builder.build()` so ensure that catalog migrations are completed
// before incremental builder migrations are executed
// this has to run after `await builder.build()` so ensure that catalog migrations are completed
// before incremental builder migrations are executed
await incrementalBuilder.build();
```

Expand All @@ -67,18 +67,18 @@ import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {

const builder = CatalogBuilder.create(env);
// incremental builder receives builder because it'll register
// incremental entity providers with the builder
// incremental entity providers with the builder
const incrementalBuilder = IncrementalCatalogBuilder.create(env, builder);

builder.addProcessor(new ScaffolderEntitiesProcessor());

const { processingEngine, router } = await builder.build();

// this has to run after `await builder.build()` so ensure that catalog migrations are completed
// before incremental builder migrations are executed
// this has to run after `await builder.build()` so ensure that catalog migrations are completed
// before incremental builder migrations are executed
await incrementalBuilder.build();

await processingEngine.start();
Expand Down Expand Up @@ -186,9 +186,9 @@ If you need to pass a token to your API, then you can create a constructor that

```ts
export class MyIncrementalEntityProvider implements IncrementalEntityProvider<Cursor, Context> {

token: string;

construtor(token: string) {
this.token = token;
}
Expand All @@ -211,9 +211,9 @@ The last step is to implement the actual `next` method that will accept the curs

```ts
export class MyIncrementalEntityProvider implements IncrementalEntityProvider<Cursor, Context> {

token: string;

construtor(token: string) {
this.token = token;
}
Expand Down Expand Up @@ -276,7 +276,7 @@ export class MyIncrementalEntityProvider implements IncrementalEntityProvider<Cu
}
```

Now that you have your new Incremental Entity Provider, we can connect it to the catalog.
Now that you have your new Incremental Entity Provider, we can connect it to the catalog.

## Adding an Incremental Entity Provider to the catalog

Expand Down
9 changes: 7 additions & 2 deletions plugins/incremental-ingestion-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,17 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/backend-tasks": "^0.3.0",
"@backstage/cli": "^0.17.0",
"@types/supertest": "^2.0.8",
"@backstage/plugin-permission-node": "^0.6.0",
"@effection/vitest": "^2.0.0",
"@types/luxon": "^2.0.4",
"@types/supertest": "^2.0.8",
"@types/uuid": "^8.3.4",
"effection": "^2.0.4",
"msw": "^0.35.0",
"supertest": "^4.0.2",
"msw": "^0.35.0"
"vitest": "^0.23.2"
},
"files": [
"dist",
Expand Down
79 changes: 79 additions & 0 deletions plugins/incremental-ingestion-backend/src/tests/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
export const backstageConfig = {
backend: {
listen: { port: 8800 },
database: {
prefix: 'graphql_tests_',
client: 'pg',
connection: {
host: 'localhost',
port: '5432',
user: 'postgres',
password: 'postgres',
},
},
baseUrl: 'http://localhost:8800',
},
catalog: {
rules: [
{
allow: [
'Component',
'System',
'API',
'Group',
'User',
'Resource',
'Location'
],
},
],
locations: [
{
type: 'url',
target:
'https://github.com/thefrontside/backstage/blob/main/catalog-info.yaml',
},
{
type: 'url',
target:
'https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml',
},
{
type: 'url',
target:
'https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml',
},
{
type: 'url',
target:
'https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml',
},
{
type: 'url',
target:
'https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml',
},
{
type: 'url',
target:
'https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml',
},
{
type: 'url',
target:
'https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml',
rules: [{ allow: ['Template'] }]
},{
type: 'url',
target:
'https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml',
rules: [{ allow: ['Template'] }]
},{
type: 'url',
target:
'https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml',
rules: [{ allow: ['Template'] }]
},
],
},
};
108 changes: 108 additions & 0 deletions plugins/incremental-ingestion-backend/src/tests/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* eslint-disable func-names */
import { describe, beforeAll, it } from '@effection/vitest';
import { TaskScheduler } from '@backstage/backend-tasks';
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
import { ensure, once, Operation } from 'effection';
import { Duration } from 'luxon';
import { createLogger, Logger, transports } from 'winston';
import { EntityIteratorResult, IncrementalCatalogBuilder } from '..';
import { IncrementalEntityProvider, IncrementalEntityProviderOptions, PluginEnvironment } from '../types';
import { ConfigReader } from '@backstage/config';
import { backstageConfig } from './config';
import { DatabaseManager, ServerTokenManager, SingleHostDiscovery, UrlReaders } from '@backstage/backend-common';

interface Page {
data: string[];
retries?: number;
timeout?: number;
}

class EntityProvider implements IncrementalEntityProvider<number, void> {
private pages: Page[] = [];

getProviderName() { return 'EntityProvider' }

async around(burst: () => Promise<void>): Promise<void> {
burst();
}

async next(_context: void, page: number): Promise<EntityIteratorResult<number>> {
// TODO Handle pages
return this.pages
}

setData(data: Page[]) { this.pages = data }
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Instead of setData, let's create a test client that we can control. The test client can be very simple but it'll be responsible for returning data or throwing errors to induce a specific state. This test client will be setup in around.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We can't call around directly to setup client there and are able to control it. So either we create client outside and we just pass client to burst in around or we setup client in around but we can't get a reference to it

}

function useCatalogPlugin(env: PluginEnvironment): Operation<EntityProvider> {
return {
name: "CatalogPlugin",
*init() {
const builder = CatalogBuilder.create(env);
const incrementalBuilder = IncrementalCatalogBuilder.create(env, builder);
const { processingEngine } = yield builder.build();
yield incrementalBuilder.build()

const provider = new EntityProvider();
const schedule: IncrementalEntityProviderOptions = {
burstInterval: Duration.fromObject({ milliseconds: 100 }),
burstLength: Duration.fromObject({ milliseconds: 100 }),
restLength: Duration.fromObject({ seconds: 1 }),
}

incrementalBuilder.addIncrementalEntityProvider(provider, schedule);

yield processingEngine.start();
yield ensure(() => processingEngine.stop());

return provider;
}
}
}

function useLogger(): Operation<Logger> {
return {
name: "Logger",
*init() {
const transport = new transports.Console();
const logger = createLogger({
level: 'error',
transports: [transport],
});
yield ensure(function* () {
logger.end();
logger.on('error', () => { /* noop */ });
yield once(logger, 'finish')
});
return logger
}
}
}

describe('incrementally ingest entities', () => {
let provider: EntityProvider

beforeAll(function* () {
const logger = yield useLogger()
const config = new ConfigReader(backstageConfig);
const reader = UrlReaders.default({ logger, config });
const databaseManager = DatabaseManager.fromConfig(config);
const discovery = SingleHostDiscovery.fromConfig(config);
const tokenManager = ServerTokenManager.noop();
const permissions = ServerPermissionClient.fromConfig(config, { discovery, tokenManager });
const scheduler = TaskScheduler.fromConfig(config).forPlugin('catalog');
provider = yield useCatalogPlugin({
logger,
database: databaseManager.forPlugin('catalog'),
config,
reader,
permissions,
scheduler
})
});

it.eventually('successfuly ingest data', function* () {
provider.setData([]);
})
})
Loading