@backstage/plugin-catalog-backend-module-incremental-ingestion 0.2.0-next.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +115 -94
- package/alpha/package.json +1 -1
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# @backstage/plugin-catalog-backend-module-incremental-ingestion
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1ba120faa3: Added new mechanism to handle deltas in incremental providers
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- c51efce2a0: Update docs to always use `yarn add --cwd` for app & backend
|
|
12
|
+
- 407dc01fc9: Removing extra imports for `run` script as `TestBackend` auto loads the default factories
|
|
13
|
+
- b7e36660d5: Return `EventSubscriber` from `addIncrementalEntityProvider` to hook up to `EventsBackend`
|
|
14
|
+
- 5b7cd5580d: Moving the backend-test-utils to devDependencies.
|
|
15
|
+
- 77c41b6924: Updated README to include newer API options for incremental entity providers
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
- @backstage/plugin-catalog-backend@1.7.2
|
|
18
|
+
- @backstage/backend-plugin-api@0.4.0
|
|
19
|
+
- @backstage/backend-common@0.18.2
|
|
20
|
+
- @backstage/catalog-model@1.2.0
|
|
21
|
+
- @backstage/plugin-events-node@0.2.3
|
|
22
|
+
- @backstage/plugin-catalog-node@1.3.3
|
|
23
|
+
- @backstage/backend-tasks@0.4.3
|
|
24
|
+
- @backstage/config@1.0.6
|
|
25
|
+
- @backstage/errors@1.1.4
|
|
26
|
+
- @backstage/plugin-permission-common@0.7.3
|
|
27
|
+
|
|
28
|
+
## 0.2.0-next.2
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- 407dc01fc9: Removing extra imports for `run` script as `TestBackend` auto loads the default factories
|
|
33
|
+
- 77c41b6924: Updated README to include newer API options for incremental entity providers
|
|
34
|
+
- Updated dependencies
|
|
35
|
+
- @backstage/backend-plugin-api@0.4.0-next.2
|
|
36
|
+
- @backstage/backend-test-utils@0.1.34-next.2
|
|
37
|
+
- @backstage/backend-common@0.18.2-next.2
|
|
38
|
+
- @backstage/plugin-catalog-backend@1.7.2-next.2
|
|
39
|
+
- @backstage/catalog-model@1.2.0-next.1
|
|
40
|
+
- @backstage/plugin-events-node@0.2.3-next.2
|
|
41
|
+
- @backstage/plugin-catalog-node@1.3.3-next.2
|
|
42
|
+
- @backstage/backend-tasks@0.4.3-next.2
|
|
43
|
+
- @backstage/config@1.0.6
|
|
44
|
+
- @backstage/errors@1.1.4
|
|
45
|
+
- @backstage/plugin-permission-common@0.7.3
|
|
46
|
+
|
|
3
47
|
## 0.2.0-next.1
|
|
4
48
|
|
|
5
49
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The Incremental Ingestion catalog backend module provides an Incremental Entity
|
|
|
4
4
|
|
|
5
5
|
## Why did we create it?
|
|
6
6
|
|
|
7
|
-
Backstage provides an [Entity Provider mechanism that has two kinds of mutations](https://backstage.io/docs/features/software-catalog/external-integrations#provider-mutations): `delta` and `full`. `delta` mutations tell Backstage Catalog which entities should be added and removed from the catalog. `full`
|
|
7
|
+
Backstage provides an [Entity Provider mechanism that has two kinds of mutations](https://backstage.io/docs/features/software-catalog/external-integrations#provider-mutations): `delta` and `full`. `delta` mutations tell the Backstage Catalog which entities should be added and removed from the catalog. `full` mutations accept a list of entities and automatically computes which entities must be removed by comparing the provided entities against existing entities to create a diff between the two sets. These two kinds of mutations are convenient for different kinds of data sources. A `delta` mutation can be used with a data source that emits UPDATE and DELETE events for its data. A `full` mutation is useful for APIs that produce fewer entities than can fit in the Backstage processes' memory.
|
|
8
8
|
|
|
9
9
|
Unfortunately, these two kinds of mutations are insufficient for very large data sources for the following reasons,
|
|
10
10
|
|
|
@@ -41,69 +41,81 @@ The Incremental Entity Provider backend is designed for data sources that provid
|
|
|
41
41
|
|
|
42
42
|
## Installation
|
|
43
43
|
|
|
44
|
-
1. Install `@backstage/plugin-catalog-backend-module-incremental-ingestion` with `yarn
|
|
45
|
-
2.
|
|
44
|
+
1. Install `@backstage/plugin-catalog-backend-module-incremental-ingestion` with `yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-incremental-ingestion` from the Backstage root directory.
|
|
45
|
+
2. In your catalog.ts, import `IncrementalCatalogBuilder` from `@backstage/plugin-catalog-backend-module-incremental-ingestion` and instantiate it with `await IncrementalCatalogBuilder.create(env, builder)`. You have to pass `builder` into `IncrementalCatalogBuilder.create` function because `IncrementalCatalogBuilder` will convert an `IncrementalEntityProvider` into an `EntityProvider` and call `builder.addEntityProvider`.
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
builder,
|
|
54
|
-
);
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
3. Last step, add `await incrementBuilder.build()` after `await builder.build()` to ensure that all `CatalogBuilder` migration run before running `incrementBuilder.build()` migrations.
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
const { processingEngine, router } = await builder.build();
|
|
61
|
-
|
|
62
|
-
// this has to run after `await builder.build()` to ensure that catalog migrations are completed
|
|
63
|
-
// before incremental builder migrations are executed
|
|
64
|
-
await incrementalBuilder.build();
|
|
65
|
-
```
|
|
47
|
+
```ts
|
|
48
|
+
const builder = CatalogBuilder.create(env);
|
|
49
|
+
// incremental builder receives builder because it'll register
|
|
50
|
+
// incremental entity providers with the builder
|
|
51
|
+
const incrementalBuilder = await IncrementalCatalogBuilder.create(env, builder);
|
|
52
|
+
```
|
|
66
53
|
|
|
67
|
-
|
|
54
|
+
3. After building the regular `CatalogBuilder`, build the incremental builder:
|
|
68
55
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
import { IncrementalCatalogBuilder } from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
|
|
73
|
-
import { Router } from 'express';
|
|
74
|
-
import { Duration } from 'luxon';
|
|
75
|
-
import { PluginEnvironment } from '../types';
|
|
56
|
+
```ts
|
|
57
|
+
// Must be run first to ensure CatalogBuilder database migrations run before Incremental Entity Provider database migrations
|
|
58
|
+
const { processingEngine, router } = await builder.build();
|
|
76
59
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const builder = CatalogBuilder.create(env);
|
|
81
|
-
// incremental builder receives builder because it'll register
|
|
82
|
-
// incremental entity providers with the builder
|
|
83
|
-
const incrementalBuilder = await IncrementalCatalogBuilder.create(
|
|
84
|
-
env,
|
|
85
|
-
builder,
|
|
86
|
-
);
|
|
60
|
+
// Returns an optional - but highly recommended - set of administrative routes
|
|
61
|
+
const { incrementalAdminRouter } = await incrementBuilder.build();
|
|
62
|
+
```
|
|
87
63
|
|
|
88
|
-
|
|
64
|
+
The final result should look something like this,
|
|
89
65
|
|
|
90
|
-
|
|
66
|
+
```ts
|
|
67
|
+
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
|
|
68
|
+
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
|
|
69
|
+
import { IncrementalCatalogBuilder } from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
|
|
70
|
+
import { Router } from 'express';
|
|
71
|
+
import { Duration } from 'luxon';
|
|
72
|
+
import { PluginEnvironment } from '../types';
|
|
73
|
+
|
|
74
|
+
export default async function createPlugin(
|
|
75
|
+
env: PluginEnvironment,
|
|
76
|
+
): Promise<Router> {
|
|
77
|
+
const builder = CatalogBuilder.create(env);
|
|
78
|
+
// incremental builder receives builder because it'll register
|
|
79
|
+
// incremental entity providers with the builder
|
|
80
|
+
const incrementalBuilder = await IncrementalCatalogBuilder.create(
|
|
81
|
+
env,
|
|
82
|
+
builder,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
builder.addProcessor(new ScaffolderEntitiesProcessor());
|
|
86
|
+
|
|
87
|
+
const { processingEngine, router } = await builder.build();
|
|
88
|
+
const { incrementalAdminRouter } = await incrementalBuilder.build();
|
|
89
|
+
|
|
90
|
+
router.use(incrementalAdminRouter);
|
|
91
|
+
|
|
92
|
+
await processingEngine.start();
|
|
93
|
+
|
|
94
|
+
return router;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
91
97
|
|
|
92
|
-
|
|
93
|
-
// before incremental builder migrations are executed
|
|
94
|
-
const { incrementalAdminRouter } = await incrementalBuilder.build();
|
|
98
|
+
## Administrative Routes
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
If you want to manage your incremental entity providers via REST endpoints, the following endpoints are available:
|
|
97
101
|
|
|
98
|
-
|
|
102
|
+
| Method | Path | Description |
|
|
103
|
+
| ------ | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| GET | `/incremental/health` | Checks the health of all incremental providers. Returns array of any unhealthy ones. |
|
|
105
|
+
| GET | `/incremental/providers/:provider` | Checks the status of an incremental provider (resting, interstitial, etc). |
|
|
106
|
+
| POST | `/incremental/providers/:provider/trigger` | Triggers a provider's next action immediately. E.g., if it's currently interstitial, it will trigger the next burst. |
|
|
107
|
+
| POST | `/incremental/providers/:provider/start` | Stop the current ingestion cycle and start a new one immediately. |
|
|
108
|
+
| POST | `/incremental/providers/:provider/cancel` | Stop the current ingestion cycle and start a new one in 24 hours. |
|
|
109
|
+
| DELETE | `/incremental/providers/:provider` | Completely remove all records for the provider and schedule it to start again in 24 hours. |
|
|
110
|
+
| GET | `/incremental/providers/:provider/marks` | Retrieve a list of all ingestion marks for the current ingestion cycle. |
|
|
111
|
+
| DELETE | `/incremental/providers/:provider/marks` | Remove all ingestion marks for the current ingestion cycle. |
|
|
112
|
+
| POST | `/incremental/cleanup` | Completely remove all records for ALL providers and schedule them to start again in 24 hours. (CAUTION! Can cause orphans!) |
|
|
99
113
|
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
```
|
|
114
|
+
In all cases, `:provider` is the name of the incremental entity provider.
|
|
103
115
|
|
|
104
116
|
## Writing an Incremental Entity Provider
|
|
105
117
|
|
|
106
|
-
To create an Incremental Entity Provider, you need to know how to retrieve a single page of the data that you wish to ingest into the Backstage catalog. If the API has pagination and you know how to make a paginated request to that API, you'll be able to implement an Incremental Entity Provider for this API. For more information about compatibility,
|
|
118
|
+
To create an Incremental Entity Provider, you need to know how to retrieve a single page of the data that you wish to ingest into the Backstage catalog. If the API has pagination and you know how to make a paginated request to that API, you'll be able to implement an Incremental Entity Provider for this API. For more information about compatibility, check out the <a href="#requirements">requirements</a> section of this page.
|
|
107
119
|
|
|
108
120
|
Here is the type definition for an Incremental Entity Provider.
|
|
109
121
|
|
|
@@ -114,33 +126,31 @@ interface IncrementalEntityProvider<TCursor, TContext> {
|
|
|
114
126
|
* operating in the catalog.
|
|
115
127
|
*/
|
|
116
128
|
getProviderName(): string;
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Do any setup and teardown necessary in order to provide the
|
|
120
|
-
* context for fetching pages. This should always invoke `burst` in
|
|
121
|
-
* order to fetch the individual pages.
|
|
122
|
-
*
|
|
123
|
-
* @param burst - a function which performs a series of iterations
|
|
124
|
-
*/
|
|
125
|
-
around(burst: (context: TContext) => Promise<void>): Promise<void>;
|
|
126
|
-
|
|
127
129
|
/**
|
|
128
130
|
* Return a single page of entities from a specific point in the
|
|
129
131
|
* ingestion.
|
|
130
132
|
*
|
|
131
133
|
* @param context - anything needed in order to fetch a single page.
|
|
132
134
|
* @param cursor - a unique value identifying the page to ingest.
|
|
133
|
-
* @returns
|
|
134
|
-
* the
|
|
135
|
+
* @returns The entities to be ingested, as well as the cursor of
|
|
136
|
+
* the next page after this one.
|
|
135
137
|
*/
|
|
136
138
|
next(
|
|
137
139
|
context: TContext,
|
|
138
140
|
cursor?: TCursor,
|
|
139
141
|
): Promise<EntityIteratorResult<TCursor>>;
|
|
142
|
+
/**
|
|
143
|
+
* Do any setup and teardown necessary in order to provide the
|
|
144
|
+
* context for fetching pages. This should always invoke `burst` in
|
|
145
|
+
* order to fetch the individual pages.
|
|
146
|
+
*
|
|
147
|
+
* @param burst - a function which performs a series of iterations
|
|
148
|
+
*/
|
|
149
|
+
around(burst: (context: TContext) => Promise<void>): Promise<void>;
|
|
140
150
|
}
|
|
141
151
|
```
|
|
142
152
|
|
|
143
|
-
For tutorial, we'll write an Incremental Entity Provider that will call an imaginary API. This imaginary API will return a list of imaginary services.
|
|
153
|
+
For this tutorial, we'll write an Incremental Entity Provider that will call an imaginary API. This imaginary API will return a list of imaginary services. The imaginary API has an imaginary API client with the following interface.
|
|
144
154
|
|
|
145
155
|
```ts
|
|
146
156
|
interface MyApiClient {
|
|
@@ -157,15 +167,12 @@ interface Service {
|
|
|
157
167
|
}
|
|
158
168
|
```
|
|
159
169
|
|
|
160
|
-
These are the only 3 methods that you need to implement. `getProviderName()` is pretty self explanatory and it's
|
|
170
|
+
These are the only 3 methods that you need to implement. `getProviderName()` is pretty self explanatory and it's identical to the `getProviderName()` method on a regular Entity Provider.
|
|
161
171
|
|
|
162
172
|
```ts
|
|
163
|
-
import {
|
|
164
|
-
IncrementalEntityProvider,
|
|
165
|
-
EntityIteratorResult,
|
|
166
|
-
} from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
|
|
173
|
+
import { IncrementalEntityProvider } from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
|
|
167
174
|
|
|
168
|
-
//
|
|
175
|
+
// This will include your pagination information, let's say our API accepts a `page` parameter.
|
|
169
176
|
// In this case, the cursor will include `page`
|
|
170
177
|
interface MyApiCursor {
|
|
171
178
|
page: number;
|
|
@@ -200,7 +207,7 @@ export class MyIncrementalEntityProvider
|
|
|
200
207
|
|
|
201
208
|
await burst({ apiClient });
|
|
202
209
|
|
|
203
|
-
//
|
|
210
|
+
// If you need to do any teardown, you can do it here.
|
|
204
211
|
}
|
|
205
212
|
}
|
|
206
213
|
```
|
|
@@ -306,31 +313,45 @@ We'll assume you followed the <a href="#installation">Installation</a> instructi
|
|
|
306
313
|
```ts
|
|
307
314
|
const incrementalBuilder = await IncrementalCatalogBuilder.create(env, builder);
|
|
308
315
|
|
|
309
|
-
//
|
|
316
|
+
// Assuming the token for the API comes from config
|
|
310
317
|
const token = config.getString('myApiClient.token');
|
|
311
318
|
|
|
312
|
-
const myEntityProvider = new MyIncrementalEntityProvider(token)
|
|
313
|
-
|
|
314
|
-
incrementalBuilder.addIncrementalEntityProvider(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
319
|
+
const myEntityProvider = new MyIncrementalEntityProvider(token);
|
|
320
|
+
|
|
321
|
+
incrementalBuilder.addIncrementalEntityProvider(myEntityProvider, {
|
|
322
|
+
// How long should it attempt to read pages from the API in a
|
|
323
|
+
// single burst? Keep this short. The Incremental Entity Provider
|
|
324
|
+
// will attempt to read as many pages as it can in this time
|
|
325
|
+
burstLength: Duration.fromObject({ seconds: 3 }),
|
|
326
|
+
|
|
327
|
+
// How long should it wait between bursts?
|
|
328
|
+
burstInterval: Duration.fromObject({ seconds: 3 }),
|
|
329
|
+
|
|
330
|
+
// How long should it rest before re-ingesting again?
|
|
331
|
+
restLength: Duration.fromObject({ day: 1 }),
|
|
332
|
+
|
|
333
|
+
// Optional back-off configuration - how long should it wait to retry
|
|
334
|
+
// in the event of an error?
|
|
335
|
+
backoff: [
|
|
336
|
+
Duration.fromObject({ seconds: 5 }),
|
|
337
|
+
Duration.fromObject({ seconds: 30 }),
|
|
338
|
+
Duration.fromObject({ minutes: 10 }),
|
|
339
|
+
Duration.fromObject({ hours: 3 }),
|
|
340
|
+
],
|
|
341
|
+
|
|
342
|
+
// Optional. Use this to prevent removal of entities above a given
|
|
343
|
+
// percentage. This can be helpful if a data source is flaky and
|
|
344
|
+
// sometimes returns a successful status, but fewer than expected
|
|
345
|
+
// assets to add or maintain in the catalog.
|
|
346
|
+
rejectRemovalsAbovePercentage: 5,
|
|
347
|
+
|
|
348
|
+
// Optional. Similar to rejectRemovalsAbovePercentage, except it
|
|
349
|
+
// applies to complete, 100% failure of a data source. If true,
|
|
350
|
+
// a data source that returns a successful status but does not
|
|
351
|
+
// provide any assets to turn into entities will have its empty
|
|
352
|
+
// data set rejected.
|
|
353
|
+
rejectEmptySourceCollections: true,
|
|
354
|
+
});
|
|
334
355
|
```
|
|
335
356
|
|
|
336
357
|
That's it!!!
|
package/alpha/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-catalog-backend-module-incremental-ingestion",
|
|
3
3
|
"description": "An entity provider for streaming large asset sources into the catalog",
|
|
4
|
-
"version": "0.2.0
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -33,16 +33,15 @@
|
|
|
33
33
|
"postpack": "backstage-cli package postpack"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@backstage/backend-common": "^0.18.2
|
|
37
|
-
"@backstage/backend-plugin-api": "^0.
|
|
38
|
-
"@backstage/backend-tasks": "^0.4.3
|
|
39
|
-
"@backstage/
|
|
40
|
-
"@backstage/catalog-model": "^1.1.6-next.0",
|
|
36
|
+
"@backstage/backend-common": "^0.18.2",
|
|
37
|
+
"@backstage/backend-plugin-api": "^0.4.0",
|
|
38
|
+
"@backstage/backend-tasks": "^0.4.3",
|
|
39
|
+
"@backstage/catalog-model": "^1.2.0",
|
|
41
40
|
"@backstage/config": "^1.0.6",
|
|
42
41
|
"@backstage/errors": "^1.1.4",
|
|
43
|
-
"@backstage/plugin-catalog-backend": "^1.7.2
|
|
44
|
-
"@backstage/plugin-catalog-node": "^1.3.3
|
|
45
|
-
"@backstage/plugin-events-node": "^0.2.3
|
|
42
|
+
"@backstage/plugin-catalog-backend": "^1.7.2",
|
|
43
|
+
"@backstage/plugin-catalog-node": "^1.3.3",
|
|
44
|
+
"@backstage/plugin-events-node": "^0.2.3",
|
|
46
45
|
"@backstage/plugin-permission-common": "^0.7.3",
|
|
47
46
|
"@types/express": "^4.17.6",
|
|
48
47
|
"@types/luxon": "^3.0.0",
|
|
@@ -55,9 +54,10 @@
|
|
|
55
54
|
"winston": "^3.2.1"
|
|
56
55
|
},
|
|
57
56
|
"devDependencies": {
|
|
58
|
-
"@backstage/backend-app-api": "^0.
|
|
59
|
-
"@backstage/
|
|
60
|
-
"@backstage/
|
|
57
|
+
"@backstage/backend-app-api": "^0.4.0",
|
|
58
|
+
"@backstage/backend-test-utils": "^0.1.34",
|
|
59
|
+
"@backstage/cli": "^0.22.2",
|
|
60
|
+
"@backstage/plugin-catalog-backend": "^1.7.2"
|
|
61
61
|
},
|
|
62
62
|
"files": [
|
|
63
63
|
"alpha",
|