@backstage/plugin-catalog-backend-module-incremental-ingestion 0.2.0-next.1 → 0.2.0-next.2

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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # @backstage/plugin-catalog-backend-module-incremental-ingestion
2
2
 
3
+ ## 0.2.0-next.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 407dc01fc9: Removing extra imports for `run` script as `TestBackend` auto loads the default factories
8
+ - 77c41b6924: Updated README to include newer API options for incremental entity providers
9
+ - Updated dependencies
10
+ - @backstage/backend-plugin-api@0.4.0-next.2
11
+ - @backstage/backend-test-utils@0.1.34-next.2
12
+ - @backstage/backend-common@0.18.2-next.2
13
+ - @backstage/plugin-catalog-backend@1.7.2-next.2
14
+ - @backstage/catalog-model@1.2.0-next.1
15
+ - @backstage/plugin-events-node@0.2.3-next.2
16
+ - @backstage/plugin-catalog-node@1.3.3-next.2
17
+ - @backstage/backend-tasks@0.4.3-next.2
18
+ - @backstage/config@1.0.6
19
+ - @backstage/errors@1.1.4
20
+ - @backstage/plugin-permission-common@0.7.3
21
+
3
22
  ## 0.2.0-next.1
4
23
 
5
24
  ### 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` mutation accepts 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 Backstage processes' memory.
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
 
@@ -42,68 +42,80 @@ The Incremental Entity Provider backend is designed for data sources that provid
42
42
  ## Installation
43
43
 
44
44
  1. Install `@backstage/plugin-catalog-backend-module-incremental-ingestion` with `yarn workspace backend add @backstage/plugin-catalog-backend-module-incremental-ingestion`
45
- 2. 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`.
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
- ```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(
52
- env,
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
- The result should look something like this,
54
+ 3. After building the regular `CatalogBuilder`, build the incremental builder:
68
55
 
69
- ```ts
70
- import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
71
- import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
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
- export default async function createPlugin(
78
- env: PluginEnvironment,
79
- ): Promise<Router> {
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
- builder.addProcessor(new ScaffolderEntitiesProcessor());
64
+ The final result should look something like this,
89
65
 
90
- const { processingEngine, router } = await builder.build();
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
- // this has to run after `await builder.build()` so ensure that catalog migrations are completed
93
- // before incremental builder migrations are executed
94
- const { incrementalAdminRouter } = await incrementalBuilder.build();
98
+ ## Administrative Routes
95
99
 
96
- router.use('/incremental', incrementalAdminRouter);
100
+ If you want to manage your incremental entity providers via REST endpoints, the following endpoints are available:
97
101
 
98
- await processingEngine.start();
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
- return router;
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, checkout <a href="#compatible-data-source">Compatible data sources</a> section on this page.
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 the entities to be ingested, as well as the cursor of
134
- * the the next page after this one.
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. This imaginary API has an imaginary API client with the following interface.
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 exactly same as on Entity Provider.
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
- // this will include your pagination information, let's say our API accepts a `page` parameter.
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
- // if you need to do any teardown, you can do it here
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
- // I'm assuming you're going to get your token from config
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
- myEntityProvider,
316
- {
317
- // how long should it attempt to read pages from the API
318
- // keep this short. Incremental Entity Provider will attempt to
319
- // read as many pages as it can in this time
320
- burstLength: Duration.fromObject({ seconds: 3 }),
321
- // how long should it wait between bursts?
322
- burstInterval: Duration.fromObject({ seconds: 3 }),
323
- // how long should it rest before re-ingesting again?
324
- restLength: Duration.fromObject({ day: 1 })
325
- // optional back-off configuration - how long should it wait to retry?
326
- backoff: [
327
- Duration.fromObject({ seconds: 5 }),
328
- Duration.fromObject({ seconds: 30 }),
329
- Duration.fromObject({ minutes: 10 }),
330
- Duration.fromObject({ hours: 3 })
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!!!
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion",
3
- "version": "0.2.0-next.1",
3
+ "version": "0.2.0-next.2",
4
4
  "main": "../dist/index.cjs.js",
5
5
  "types": "../dist/index.alpha.d.ts"
6
6
  }
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-next.1",
4
+ "version": "0.2.0-next.2",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "Apache-2.0",
@@ -33,16 +33,16 @@
33
33
  "postpack": "backstage-cli package postpack"
34
34
  },
35
35
  "dependencies": {
36
- "@backstage/backend-common": "^0.18.2-next.1",
37
- "@backstage/backend-plugin-api": "^0.3.2-next.1",
38
- "@backstage/backend-tasks": "^0.4.3-next.1",
39
- "@backstage/backend-test-utils": "^0.1.34-next.1",
40
- "@backstage/catalog-model": "^1.1.6-next.0",
36
+ "@backstage/backend-common": "^0.18.2-next.2",
37
+ "@backstage/backend-plugin-api": "^0.4.0-next.2",
38
+ "@backstage/backend-tasks": "^0.4.3-next.2",
39
+ "@backstage/backend-test-utils": "^0.1.34-next.2",
40
+ "@backstage/catalog-model": "^1.2.0-next.1",
41
41
  "@backstage/config": "^1.0.6",
42
42
  "@backstage/errors": "^1.1.4",
43
- "@backstage/plugin-catalog-backend": "^1.7.2-next.1",
44
- "@backstage/plugin-catalog-node": "^1.3.3-next.1",
45
- "@backstage/plugin-events-node": "^0.2.3-next.1",
43
+ "@backstage/plugin-catalog-backend": "^1.7.2-next.2",
44
+ "@backstage/plugin-catalog-node": "^1.3.3-next.2",
45
+ "@backstage/plugin-events-node": "^0.2.3-next.2",
46
46
  "@backstage/plugin-permission-common": "^0.7.3",
47
47
  "@types/express": "^4.17.6",
48
48
  "@types/luxon": "^3.0.0",
@@ -55,9 +55,9 @@
55
55
  "winston": "^3.2.1"
56
56
  },
57
57
  "devDependencies": {
58
- "@backstage/backend-app-api": "^0.3.2-next.1",
59
- "@backstage/cli": "^0.22.2-next.0",
60
- "@backstage/plugin-catalog-backend": "^1.7.2-next.1"
58
+ "@backstage/backend-app-api": "^0.4.0-next.2",
59
+ "@backstage/cli": "^0.22.2-next.1",
60
+ "@backstage/plugin-catalog-backend": "^1.7.2-next.2"
61
61
  },
62
62
  "files": [
63
63
  "alpha",