@backstage-community/plugin-3scale-backend 1.8.5 → 3.0.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 +12 -0
- package/README.md +3 -78
- package/config.d.ts +18 -2
- package/dist/index.cjs.js +392 -22
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +11 -9
- package/dist-dynamic/README.md +61 -0
- package/dist-dynamic/app-config.yaml +11 -0
- package/dist-dynamic/config.d.ts +38 -0
- package/dist-dynamic/package.json +58 -0
- package/dist-dynamic/yarn.lock +16 -0
- package/package.json +14 -16
- package/alpha/package.json +0 -6
- package/dist/alpha.cjs.js +0 -39
- package/dist/alpha.cjs.js.map +0 -1
- package/dist/alpha.d.ts +0 -5
- package/dist/cjs/ThreeScaleApiEntityProvider-B3TyzFXk.cjs.js +0 -246
- package/dist/cjs/ThreeScaleApiEntityProvider-B3TyzFXk.cjs.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
## @janus-idp/backstage-plugin-3scale-backend [1.8.0](https://github.com/janus-idp/backstage-plugins/compare/@janus-idp/backstage-plugin-3scale-backend@1.7.1...@janus-idp/backstage-plugin-3scale-backend@1.8.0) (2024-07-25)
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- d8c8238: Merge API docs for the same service.
|
|
8
|
+
|
|
9
|
+
## 2.0.0
|
|
10
|
+
|
|
11
|
+
### Major Changes
|
|
12
|
+
|
|
13
|
+
- 577034b: **BREAKING** migrate to the new backend and remove deprecations
|
|
14
|
+
|
|
3
15
|
## 1.8.5
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ yarn workspace backend add @backstage-community/plugin-3scale-backend
|
|
|
16
16
|
|
|
17
17
|
3scale Backstage provider allows configuration of one or multiple providers using the `app-config.yaml` configuration file of Backstage.
|
|
18
18
|
|
|
19
|
-
####
|
|
19
|
+
#### New Backend Procedure
|
|
20
20
|
|
|
21
21
|
1. Use a `threeScaleApiEntity` marker to start configuring the `app-config.yaml` file of Backstage:
|
|
22
22
|
|
|
@@ -34,91 +34,16 @@ yarn workspace backend add @backstage-community/plugin-3scale-backend
|
|
|
34
34
|
timeout: { minutes: 3 }
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
2. If installing into the _legacy_ backend, configure the scheduler for the entity provider using one of the following methods:
|
|
38
|
-
|
|
39
|
-
- **Method 1**: If the scheduler is configured inside the `app-config.yaml` using the schedule config key mentioned previously, add the following code to `packages/backend/src/plugins/catalog.ts` file:
|
|
40
|
-
|
|
41
|
-
```ts title="packages/backend/src/plugins/catalog.ts"
|
|
42
|
-
/* highlight-add-next-line */
|
|
43
|
-
import { ThreeScaleApiEntityProvider } from '@backstage-community/plugin-3scale-backend';
|
|
44
|
-
|
|
45
|
-
export default async function createPlugin(
|
|
46
|
-
env: PluginEnvironment,
|
|
47
|
-
): Promise<Router> {
|
|
48
|
-
const builder = await CatalogBuilder.create(env);
|
|
49
|
-
|
|
50
|
-
/* ... other processors and/or providers ... */
|
|
51
|
-
/* highlight-add-start */
|
|
52
|
-
builder.addEntityProvider(
|
|
53
|
-
ThreeScaleApiEntityProvider.fromConfig(env.config, {
|
|
54
|
-
logger: env.logger,
|
|
55
|
-
scheduler: env.scheduler,
|
|
56
|
-
}),
|
|
57
|
-
);
|
|
58
|
-
/* highlight-add-end */
|
|
59
|
-
|
|
60
|
-
const { processingEngine, router } = await builder.build();
|
|
61
|
-
await processingEngine.start();
|
|
62
|
-
return router;
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
***
|
|
67
|
-
|
|
68
|
-
**NOTE**
|
|
69
|
-
|
|
70
|
-
If you have made any changes to the schedule in the `app-config.yaml` file, then restart to apply the changes.
|
|
71
|
-
|
|
72
|
-
***
|
|
73
|
-
|
|
74
|
-
- **Method 2**: Add a schedule directly inside the `packages/backend/src/plugins/catalog.ts` file as follows:
|
|
75
|
-
|
|
76
|
-
```ts title="packages/backend/src/plugins/catalog.ts"
|
|
77
|
-
/* highlight-add-next-line */
|
|
78
|
-
import { ThreeScaleApiEntityProvider } from '@backstage-community/plugin-3scale-backend';
|
|
79
|
-
|
|
80
|
-
export default async function createPlugin(
|
|
81
|
-
env: PluginEnvironment,
|
|
82
|
-
): Promise<Router> {
|
|
83
|
-
const builder = await CatalogBuilder.create(env);
|
|
84
|
-
|
|
85
|
-
/* ... other processors and/or providers ... */
|
|
86
|
-
/* highlight-add-start */
|
|
87
|
-
builder.addEntityProvider(
|
|
88
|
-
ThreeScaleApiEntityProvider.fromConfig(env.config, {
|
|
89
|
-
logger: env.logger,
|
|
90
|
-
schedule: env.scheduler.createScheduledTaskRunner({
|
|
91
|
-
frequency: { minutes: 30 },
|
|
92
|
-
timeout: { minutes: 3 },
|
|
93
|
-
}),
|
|
94
|
-
}),
|
|
95
|
-
);
|
|
96
|
-
/* highlight-add-end */
|
|
97
|
-
|
|
98
|
-
const { processingEngine, router } = await builder.build();
|
|
99
|
-
await processingEngine.start();
|
|
100
|
-
return router;
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
***
|
|
105
|
-
|
|
106
37
|
**NOTE**
|
|
38
|
+
Make sure to configure the schedule inside the `app-config.yaml` file. The default schedule is a frequency of 30 minutes and a timeout of 3 minutes.
|
|
107
39
|
|
|
108
|
-
If both the `schedule` (hard-coded schedule) and `scheduler` (`app-config.yaml` schedule) option are provided in the `packages/backend/src/plugins/catalog.ts`, the `scheduler` option takes precedence. However, if the schedule inside the `app-config.yaml` file is not configured, then the `schedule` option is used.
|
|
109
|
-
|
|
110
|
-
***
|
|
111
|
-
|
|
112
|
-
#### New Backend Procedure
|
|
113
|
-
|
|
114
|
-
1. If installing into the new backend system, make the same configurations to the `app=config.yaml` as in the [Legacy Backend Installation Procedure](#legacy-backend-installation-procedure). Make sure to configure the schedule inside the `app-config.yaml` file. The default schedule is a frequency of 30 minutes and a timeout of 3 minutes.
|
|
115
40
|
2. Add the following code to the `packages/backend/src/index.ts` file:
|
|
116
41
|
|
|
117
42
|
```ts title="packages/backend/src/index.ts"
|
|
118
43
|
const backend = createBackend();
|
|
119
44
|
|
|
120
45
|
/* highlight-add-next-line */
|
|
121
|
-
backend.add(import('@backstage-community/plugin-3scale-backend
|
|
46
|
+
backend.add(import('@backstage-community/plugin-3scale-backend'));
|
|
122
47
|
|
|
123
48
|
backend.start();
|
|
124
49
|
```
|
package/config.d.ts
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 The Backstage Authors
|
|
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
|
+
|
|
17
|
+
import type { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api';
|
|
2
18
|
|
|
3
19
|
export interface Config {
|
|
4
20
|
catalog?: {
|
|
@@ -14,7 +30,7 @@ export interface Config {
|
|
|
14
30
|
systemLabel?: string;
|
|
15
31
|
ownerLabel?: string;
|
|
16
32
|
addLabels?: boolean;
|
|
17
|
-
schedule?:
|
|
33
|
+
schedule?: SchedulerServiceTaskScheduleDefinitionConfig;
|
|
18
34
|
};
|
|
19
35
|
};
|
|
20
36
|
};
|
package/dist/index.cjs.js
CHANGED
|
@@ -1,28 +1,398 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require('@backstage/backend-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
6
|
+
var alpha = require('@backstage/plugin-catalog-node/alpha');
|
|
7
|
+
var catalogModel = require('@backstage/catalog-model');
|
|
8
|
+
var errors = require('@backstage/errors');
|
|
9
|
+
var openapiMerge = require('openapi-merge');
|
|
10
|
+
var Swagger2OpenAPI = require('swagger2openapi');
|
|
11
|
+
var SwaggerConverter = require('swagger-converter');
|
|
12
|
+
|
|
13
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
14
|
+
|
|
15
|
+
var Swagger2OpenAPI__default = /*#__PURE__*/_interopDefaultCompat(Swagger2OpenAPI);
|
|
16
|
+
var SwaggerConverter__default = /*#__PURE__*/_interopDefaultCompat(SwaggerConverter);
|
|
17
|
+
|
|
18
|
+
function listServices(baseUrl, access_token, page, size) {
|
|
19
|
+
return fetch(
|
|
20
|
+
`${baseUrl}/admin/api/services.json?access_token=${access_token}&page=${page}&size=${size}`
|
|
21
|
+
).then((response) => {
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(response.statusText);
|
|
24
|
+
}
|
|
25
|
+
return response.json();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function listApiDocs(baseUrl, access_token) {
|
|
29
|
+
return fetch(
|
|
30
|
+
`${baseUrl}/admin/api/active_docs.json?access_token=${access_token}`
|
|
31
|
+
).then((response) => {
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(response.statusText);
|
|
34
|
+
}
|
|
35
|
+
return response.json();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function getProxyConfig(baseUrl, access_token, service_id) {
|
|
39
|
+
return fetch(
|
|
40
|
+
`${baseUrl}/admin/api/services/${service_id}/proxy.json?access_token=${access_token}`
|
|
41
|
+
).then((response) => {
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(response.statusText);
|
|
44
|
+
}
|
|
45
|
+
return response.json();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readThreeScaleApiEntityConfigs(config) {
|
|
50
|
+
const providerConfigs = config.getOptionalConfig(
|
|
51
|
+
"catalog.providers.threeScaleApiEntity"
|
|
52
|
+
);
|
|
53
|
+
if (!providerConfigs) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
return providerConfigs.keys().map(
|
|
57
|
+
(id) => readThreeScaleApiEntityConfig(id, providerConfigs.getConfig(id))
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
function readThreeScaleApiEntityConfig(id, config) {
|
|
61
|
+
const baseUrl = config.getString("baseUrl");
|
|
62
|
+
const accessToken = config.getString("accessToken");
|
|
63
|
+
const systemLabel = config.getOptionalString("systemLabel");
|
|
64
|
+
const ownerLabel = config.getOptionalString("ownerLabel");
|
|
65
|
+
const addLabels = config.getOptionalBoolean("addLabels") || true;
|
|
66
|
+
const schedule = config.has("schedule") ? backendPluginApi.readSchedulerServiceTaskScheduleDefinitionFromConfig(
|
|
67
|
+
config.getConfig("schedule")
|
|
68
|
+
) : void 0;
|
|
69
|
+
return {
|
|
70
|
+
id,
|
|
71
|
+
baseUrl,
|
|
72
|
+
accessToken,
|
|
73
|
+
systemLabel,
|
|
74
|
+
ownerLabel,
|
|
75
|
+
addLabels,
|
|
76
|
+
schedule
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isNonEmptyArray(arr) {
|
|
81
|
+
return arr.length > 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isSwagger1_2(apiDoc) {
|
|
85
|
+
return apiDoc.swaggerVersion && apiDoc.swaggerVersion === "1.2";
|
|
86
|
+
}
|
|
87
|
+
function isSwagger2_0(apiDoc) {
|
|
88
|
+
return apiDoc.swagger && apiDoc.swagger === "2.0";
|
|
89
|
+
}
|
|
90
|
+
function isOpenAPI3_0(apiDoc) {
|
|
91
|
+
return apiDoc.openapi;
|
|
92
|
+
}
|
|
93
|
+
class OpenAPIMergerAndConverter {
|
|
94
|
+
async mergeOpenAPI3Docs(docs) {
|
|
95
|
+
const mergeInput = docs.map((doc) => {
|
|
96
|
+
return { oas: doc };
|
|
97
|
+
});
|
|
98
|
+
const result = await openapiMerge.merge(mergeInput);
|
|
99
|
+
if (openapiMerge.isErrorResult(result)) {
|
|
100
|
+
throw new Error(result.message);
|
|
101
|
+
}
|
|
102
|
+
return result.output;
|
|
103
|
+
}
|
|
104
|
+
// Convert api doc to format openAPI 3. Do nothing with doc if it has format openAPI 3.0.
|
|
105
|
+
// 3scale supports API docs in formats:
|
|
106
|
+
// - swagger 1.2
|
|
107
|
+
// - swagger 2.0
|
|
108
|
+
// - openAPI 3.0
|
|
109
|
+
async convertAPIDocToOpenAPI3(apiDoc) {
|
|
110
|
+
if (isOpenAPI3_0(apiDoc)) {
|
|
111
|
+
return apiDoc;
|
|
112
|
+
}
|
|
113
|
+
if (isSwagger1_2(apiDoc)) {
|
|
114
|
+
const swagger2_0Doc = await this.convertSwagger1_2To2_0(apiDoc);
|
|
115
|
+
return await this.convertSwagger2_0ToOpenAPI3_0(swagger2_0Doc);
|
|
116
|
+
}
|
|
117
|
+
if (isSwagger2_0(apiDoc)) {
|
|
118
|
+
return await this.convertSwagger2_0ToOpenAPI3_0(apiDoc);
|
|
119
|
+
}
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Unsupported API document. Plugin supports Swagger 1.2, 2.0, 3.0(Open API 3.0)`
|
|
19
122
|
);
|
|
20
123
|
}
|
|
21
|
-
|
|
124
|
+
async convertSwagger1_2To2_0(swaggerDoc) {
|
|
125
|
+
try {
|
|
126
|
+
const result = SwaggerConverter__default.default.convert(swaggerDoc, {});
|
|
127
|
+
return result;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error("Error converting Swagger 1.2 to Swagger 2.0:", error);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async convertSwagger2_0ToOpenAPI3_0(swaggerDoc) {
|
|
134
|
+
try {
|
|
135
|
+
const result = await Swagger2OpenAPI__default.default.convertObj(swaggerDoc, {
|
|
136
|
+
patch: true,
|
|
137
|
+
// patch: true helps to fix minor issues
|
|
138
|
+
warnOnly: true
|
|
139
|
+
// Do not throw on non-patchable errors
|
|
140
|
+
});
|
|
141
|
+
return result.openapi;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error("Error converting Swagger 2.0 to OpenAPI 3.0:", error);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
class ThreeScaleApiEntityProvider {
|
|
150
|
+
static SERVICES_FETCH_SIZE = 500;
|
|
151
|
+
env;
|
|
152
|
+
baseUrl;
|
|
153
|
+
accessToken;
|
|
154
|
+
logger;
|
|
155
|
+
scheduleFn;
|
|
156
|
+
openApiMerger;
|
|
157
|
+
connection;
|
|
158
|
+
static fromConfig(deps, options) {
|
|
159
|
+
const providerConfigs = readThreeScaleApiEntityConfigs(deps.config);
|
|
160
|
+
if (!options.schedule && !options.scheduler) {
|
|
161
|
+
throw new Error("Either schedule or scheduler must be provided.");
|
|
162
|
+
}
|
|
163
|
+
return providerConfigs.map((providerConfig) => {
|
|
164
|
+
if (!options.schedule && !providerConfig.schedule) {
|
|
165
|
+
throw new errors.InputError(
|
|
166
|
+
`No schedule provided via config for ThreeScaleApiEntityProvider:${providerConfig.id}.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
let taskRunner;
|
|
170
|
+
if (options.scheduler && providerConfig.schedule) {
|
|
171
|
+
taskRunner = options.scheduler.createScheduledTaskRunner(
|
|
172
|
+
providerConfig.schedule
|
|
173
|
+
);
|
|
174
|
+
} else if (options.schedule) {
|
|
175
|
+
taskRunner = options.schedule;
|
|
176
|
+
} else {
|
|
177
|
+
throw new Error("Neither schedule nor scheduler is provided.");
|
|
178
|
+
}
|
|
179
|
+
return new ThreeScaleApiEntityProvider(
|
|
180
|
+
providerConfig,
|
|
181
|
+
deps.logger,
|
|
182
|
+
taskRunner
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
constructor(config, logger, taskRunner) {
|
|
187
|
+
this.env = config.id;
|
|
188
|
+
this.baseUrl = config.baseUrl;
|
|
189
|
+
this.accessToken = config.accessToken;
|
|
190
|
+
this.logger = logger.child({
|
|
191
|
+
target: this.getProviderName()
|
|
192
|
+
});
|
|
193
|
+
this.scheduleFn = this.createScheduleFn(taskRunner);
|
|
194
|
+
this.openApiMerger = new OpenAPIMergerAndConverter();
|
|
195
|
+
}
|
|
196
|
+
createScheduleFn(taskRunner) {
|
|
197
|
+
return async () => {
|
|
198
|
+
const taskId = `${this.getProviderName()}:run`;
|
|
199
|
+
return taskRunner.run({
|
|
200
|
+
id: taskId,
|
|
201
|
+
fn: async () => {
|
|
202
|
+
try {
|
|
203
|
+
await this.run();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (errors.isError(error)) {
|
|
206
|
+
this.logger.error(
|
|
207
|
+
`Error while syncing 3scale API from ${this.baseUrl}`,
|
|
208
|
+
{
|
|
209
|
+
// Default Error properties:
|
|
210
|
+
name: error.name,
|
|
211
|
+
message: error.message,
|
|
212
|
+
stack: error.stack,
|
|
213
|
+
// Additional status code if available:
|
|
214
|
+
status: error.response?.status
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
getProviderName() {
|
|
224
|
+
return `ThreeScaleApiEntityProvider:${this.env}`;
|
|
225
|
+
}
|
|
226
|
+
async connect(connection) {
|
|
227
|
+
this.connection = connection;
|
|
228
|
+
await this.scheduleFn();
|
|
229
|
+
}
|
|
230
|
+
async run() {
|
|
231
|
+
if (!this.connection) {
|
|
232
|
+
throw new errors.NotFoundError("Not initialized");
|
|
233
|
+
}
|
|
234
|
+
this.logger.info(`Discovering ApiEntities from 3scale ${this.baseUrl}`);
|
|
235
|
+
const entities = [];
|
|
236
|
+
let page = 0;
|
|
237
|
+
let services;
|
|
238
|
+
let apiDocs;
|
|
239
|
+
let fetchServices = true;
|
|
240
|
+
while (fetchServices) {
|
|
241
|
+
services = await listServices(
|
|
242
|
+
this.baseUrl,
|
|
243
|
+
this.accessToken,
|
|
244
|
+
page,
|
|
245
|
+
ThreeScaleApiEntityProvider.SERVICES_FETCH_SIZE
|
|
246
|
+
);
|
|
247
|
+
apiDocs = await listApiDocs(this.baseUrl, this.accessToken);
|
|
248
|
+
for (const element of services.services) {
|
|
249
|
+
const service = element;
|
|
250
|
+
this.logger.debug(`Find service ${service.service.name}`);
|
|
251
|
+
const docs = apiDocs.api_docs.filter(
|
|
252
|
+
(obj) => obj.api_doc.service_id === service.service.id
|
|
253
|
+
);
|
|
254
|
+
const proxy = await getProxyConfig(
|
|
255
|
+
this.baseUrl,
|
|
256
|
+
this.accessToken,
|
|
257
|
+
service.service.id
|
|
258
|
+
);
|
|
259
|
+
if (isNonEmptyArray(docs)) {
|
|
260
|
+
this.logger.info(JSON.stringify(docs));
|
|
261
|
+
const apiEntity = await this.buildApiEntityFromService(
|
|
262
|
+
service,
|
|
263
|
+
docs,
|
|
264
|
+
proxy
|
|
265
|
+
);
|
|
266
|
+
entities.push(apiEntity);
|
|
267
|
+
this.logger.debug(`Discovered ApiEntity ${service.service.name}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (services.services.length < ThreeScaleApiEntityProvider.SERVICES_FETCH_SIZE) {
|
|
271
|
+
fetchServices = false;
|
|
272
|
+
}
|
|
273
|
+
page++;
|
|
274
|
+
}
|
|
275
|
+
this.logger.info(`Applying the mutation with ${entities.length} entities`);
|
|
276
|
+
await this.connection.applyMutation({
|
|
277
|
+
type: "full",
|
|
278
|
+
entities: entities.map((entity) => ({
|
|
279
|
+
entity,
|
|
280
|
+
locationKey: this.getProviderName()
|
|
281
|
+
}))
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
async buildApiEntityFromService(service, apiDocs, proxy) {
|
|
285
|
+
const location = `url:${this.baseUrl}/apiconfig/services/${service.service.id}`;
|
|
286
|
+
const serviceDescription = service.service.description || "";
|
|
287
|
+
let entityDescription;
|
|
288
|
+
const docs = apiDocs.map((doc) => JSON.parse(doc.api_doc.body));
|
|
289
|
+
let swaggerDocJSON;
|
|
290
|
+
if (docs.length > 1) {
|
|
291
|
+
let mergedDescription = `[Merged ${docs.length} API docs]`;
|
|
292
|
+
let mergedTitle = mergedDescription;
|
|
293
|
+
const convertedDocs = [];
|
|
294
|
+
for (const doc of docs) {
|
|
295
|
+
const convertedDoc = await this.openApiMerger.convertAPIDocToOpenAPI3(
|
|
296
|
+
doc
|
|
297
|
+
);
|
|
298
|
+
convertedDocs.push(convertedDoc);
|
|
299
|
+
mergedDescription = getDocInfo(convertedDoc)?.description ? `${mergedDescription} ${getDocInfo(convertedDoc)?.description}` : mergedDescription;
|
|
300
|
+
mergedTitle = getDocInfo(convertedDoc)?.title ? `${mergedTitle} ${getDocInfo(convertedDoc)?.title}` : mergedTitle;
|
|
301
|
+
}
|
|
302
|
+
if (isNonEmptyArray(convertedDocs)) {
|
|
303
|
+
swaggerDocJSON = await this.openApiMerger.mergeOpenAPI3Docs(
|
|
304
|
+
convertedDocs
|
|
305
|
+
);
|
|
306
|
+
swaggerDocJSON.info.description = mergedDescription;
|
|
307
|
+
swaggerDocJSON.info.title = mergedTitle;
|
|
308
|
+
entityDescription = mergedDescription;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (docs.length === 1) {
|
|
312
|
+
swaggerDocJSON = docs[0];
|
|
313
|
+
const spec = JSON.parse(apiDocs[0].api_doc.body);
|
|
314
|
+
if (isSwagger1_2(spec)) {
|
|
315
|
+
swaggerDocJSON = await this.openApiMerger.convertSwagger1_2To2_0(spec);
|
|
316
|
+
}
|
|
317
|
+
entityDescription = getDocInfo(spec)?.description;
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
kind: "API",
|
|
321
|
+
apiVersion: "backstage.io/v1alpha1",
|
|
322
|
+
metadata: {
|
|
323
|
+
annotations: {
|
|
324
|
+
[catalogModel.ANNOTATION_LOCATION]: location,
|
|
325
|
+
[catalogModel.ANNOTATION_ORIGIN_LOCATION]: location
|
|
326
|
+
},
|
|
327
|
+
// TODO: add tenant name
|
|
328
|
+
name: `${service.service.system_name}`,
|
|
329
|
+
description: entityDescription || serviceDescription,
|
|
330
|
+
// TODO: add labels
|
|
331
|
+
// labels: this.getApiEntityLabels(service),
|
|
332
|
+
links: [
|
|
333
|
+
{
|
|
334
|
+
url: `${this.baseUrl}/apiconfig/services/${service.service.id}`,
|
|
335
|
+
title: "3scale Overview"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
url: `${proxy.proxy.sandbox_endpoint}`,
|
|
339
|
+
title: "Staging Apicast Endpoint"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
url: `${proxy.proxy.endpoint}`,
|
|
343
|
+
title: "Production Apicast Endpoint"
|
|
344
|
+
}
|
|
345
|
+
]
|
|
346
|
+
},
|
|
347
|
+
spec: {
|
|
348
|
+
type: "openapi",
|
|
349
|
+
lifecycle: this.env,
|
|
350
|
+
system: "3scale",
|
|
351
|
+
owner: "3scale",
|
|
352
|
+
definition: JSON.stringify(swaggerDocJSON, null, 2)
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function getDocInfo(spec) {
|
|
358
|
+
if (isSwagger2_0(spec) || isOpenAPI3_0(spec)) {
|
|
359
|
+
return spec.info;
|
|
360
|
+
}
|
|
361
|
+
return void 0;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const catalogModule3ScaleEntityProvider = backendPluginApi.createBackendModule({
|
|
365
|
+
moduleId: "catalog-backend-module-3scale",
|
|
366
|
+
pluginId: "catalog",
|
|
367
|
+
register(env) {
|
|
368
|
+
env.registerInit({
|
|
369
|
+
deps: {
|
|
370
|
+
catalog: alpha.catalogProcessingExtensionPoint,
|
|
371
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
372
|
+
logger: backendPluginApi.coreServices.logger,
|
|
373
|
+
scheduler: backendPluginApi.coreServices.scheduler
|
|
374
|
+
},
|
|
375
|
+
async init({ catalog, config, logger, scheduler }) {
|
|
376
|
+
catalog.addEntityProvider(
|
|
377
|
+
ThreeScaleApiEntityProvider.fromConfig(
|
|
378
|
+
{ config, logger },
|
|
379
|
+
{
|
|
380
|
+
scheduler,
|
|
381
|
+
schedule: scheduler.createScheduledTaskRunner({
|
|
382
|
+
frequency: { minutes: 30 },
|
|
383
|
+
timeout: { minutes: 3 }
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
)
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
});
|
|
22
392
|
|
|
23
|
-
exports.ThreeScaleApiEntityProvider = ThreeScaleApiEntityProvider
|
|
24
|
-
exports.
|
|
25
|
-
exports.
|
|
26
|
-
exports.
|
|
27
|
-
exports.
|
|
393
|
+
exports.ThreeScaleApiEntityProvider = ThreeScaleApiEntityProvider;
|
|
394
|
+
exports.default = catalogModule3ScaleEntityProvider;
|
|
395
|
+
exports.getProxyConfig = getProxyConfig;
|
|
396
|
+
exports.listApiDocs = listApiDocs;
|
|
397
|
+
exports.listServices = listServices;
|
|
28
398
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/dynamic/index.ts"],"sourcesContent":["/*\n * Copyright 2023 The Janus IDP Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { BackendDynamicPluginInstaller } from '@backstage/backend-dynamic-feature-service';\n\nimport { ThreeScaleApiEntityProvider } from '../providers';\n\nexport const dynamicPluginInstaller: BackendDynamicPluginInstaller = {\n kind: 'legacy',\n async catalog(builder, env) {\n builder.addEntityProvider(\n ThreeScaleApiEntityProvider.fromConfig(env.config, {\n logger: env.logger,\n scheduler: env.scheduler,\n schedule: env.scheduler.createScheduledTaskRunner({\n frequency: { minutes: 1 },\n timeout: { minutes: 1 },\n }),\n }),\n );\n },\n};\n"],"names":["ThreeScaleApiEntityProvider"],"mappings":";;;;;;AAmBO,MAAM,sBAAwD,GAAA;AAAA,EACnE,IAAM,EAAA,QAAA;AAAA,EACN,MAAM,OAAQ,CAAA,OAAA,EAAS,GAAK,EAAA;AAC1B,IAAQ,OAAA,CAAA,iBAAA;AAAA,MACNA,uDAAA,CAA4B,UAAW,CAAA,GAAA,CAAI,MAAQ,EAAA;AAAA,QACjD,QAAQ,GAAI,CAAA,MAAA;AAAA,QACZ,WAAW,GAAI,CAAA,SAAA;AAAA,QACf,QAAA,EAAU,GAAI,CAAA,SAAA,CAAU,yBAA0B,CAAA;AAAA,UAChD,SAAA,EAAW,EAAE,OAAA,EAAS,CAAE,EAAA;AAAA,UACxB,OAAA,EAAS,EAAE,OAAA,EAAS,CAAE,EAAA;AAAA,SACvB,CAAA;AAAA,OACF,CAAA;AAAA,KACH,CAAA;AAAA,GACF;AACF;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/clients/ThreeScaleAPIConnector.ts","../src/providers/config.ts","../src/providers/types.ts","../src/providers/open-api-merger-converter.ts","../src/providers/ThreeScaleApiEntityProvider.ts","../src/module.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { APIDocs, Proxy, Services } from './types';\n\nexport function listServices(\n baseUrl: string,\n access_token: string,\n page: number,\n size: number,\n): Promise<Services> {\n return fetch(\n `${baseUrl}/admin/api/services.json?access_token=${access_token}&page=${page}&size=${size}`,\n ).then(response => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.json() as Promise<Services>;\n });\n}\n\nexport function listApiDocs(\n baseUrl: string,\n access_token: string,\n): Promise<APIDocs> {\n return fetch(\n `${baseUrl}/admin/api/active_docs.json?access_token=${access_token}`,\n ).then(response => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.json() as Promise<APIDocs>;\n });\n}\n\nexport function getProxyConfig(\n baseUrl: string,\n access_token: string,\n service_id: number,\n): Promise<Proxy> {\n return fetch(\n `${baseUrl}/admin/api/services/${service_id}/proxy.json?access_token=${access_token}`,\n ).then(response => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.json() as Promise<Proxy>;\n });\n}\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\n\nimport { ThreeScaleConfig } from './types';\n\nexport function readThreeScaleApiEntityConfigs(\n config: Config,\n): ThreeScaleConfig[] {\n const providerConfigs = config.getOptionalConfig(\n 'catalog.providers.threeScaleApiEntity',\n );\n if (!providerConfigs) {\n return [];\n }\n return providerConfigs\n .keys()\n .map(id =>\n readThreeScaleApiEntityConfig(id, providerConfigs.getConfig(id)),\n );\n}\n\nfunction readThreeScaleApiEntityConfig(\n id: string,\n config: Config,\n): ThreeScaleConfig {\n const baseUrl = config.getString('baseUrl');\n const accessToken = config.getString('accessToken');\n const systemLabel = config.getOptionalString('systemLabel');\n const ownerLabel = config.getOptionalString('ownerLabel');\n const addLabels = config.getOptionalBoolean('addLabels') || true;\n\n const schedule = config.has('schedule')\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n config.getConfig('schedule'),\n )\n : undefined;\n\n return {\n id,\n baseUrl,\n accessToken,\n systemLabel,\n ownerLabel,\n addLabels,\n schedule,\n };\n}\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api';\n\nexport type ThreeScaleConfig = {\n id: string;\n baseUrl: string;\n accessToken: string;\n systemLabel?: string;\n ownerLabel?: string;\n addLabels?: boolean;\n schedule?: SchedulerServiceTaskScheduleDefinition;\n};\n\nexport type NonEmptyArray<T> = [T, ...T[]];\n\nexport function isNonEmptyArray<T>(arr: T[]): arr is NonEmptyArray<T> {\n return arr.length > 0;\n}\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { merge, isErrorResult, MergeInput } from 'openapi-merge';\nimport { Swagger } from 'atlassian-openapi';\nimport Swagger2OpenAPI from 'swagger2openapi';\n// @ts-ignore\nimport SwaggerConverter from 'swagger-converter';\nimport { NonEmptyArray } from './types';\n\nexport function isSwagger1_2(apiDoc: any): boolean {\n return apiDoc.swaggerVersion && apiDoc.swaggerVersion === '1.2';\n}\n\nexport function isSwagger2_0(apiDoc: any): boolean {\n return apiDoc.swagger && apiDoc.swagger === '2.0';\n}\n\nexport function isOpenAPI3_0(apiDoc: any): boolean {\n return apiDoc.openapi;\n}\n\nexport class OpenAPIMergerAndConverter {\n async mergeOpenAPI3Docs(\n docs: NonEmptyArray<Swagger.SwaggerV3>,\n ): Promise<Swagger.SwaggerV3> {\n const mergeInput: MergeInput = docs.map(doc => {\n return { oas: doc };\n });\n\n const result = await merge(mergeInput);\n if (isErrorResult(result)) {\n throw new Error(result.message);\n }\n return result.output;\n }\n\n // Convert api doc to format openAPI 3. Do nothing with doc if it has format openAPI 3.0.\n // 3scale supports API docs in formats:\n // - swagger 1.2\n // - swagger 2.0\n // - openAPI 3.0\n async convertAPIDocToOpenAPI3(apiDoc: any): Promise<Swagger.SwaggerV3> {\n if (isOpenAPI3_0(apiDoc)) {\n return apiDoc;\n }\n if (isSwagger1_2(apiDoc)) {\n // Unfortunately there is no library in the JavaScript world, which can convert both swagger 1.2 and 2.0 to openAPI 3.0.\n // That's why, for swagger 1.2 we are using convertation to swagger 2.0. And then swagger 2.0 will be converted to openAPI 3.0.\n const swagger2_0Doc = await this.convertSwagger1_2To2_0(apiDoc);\n return await this.convertSwagger2_0ToOpenAPI3_0(swagger2_0Doc);\n }\n if (isSwagger2_0(apiDoc)) {\n return await this.convertSwagger2_0ToOpenAPI3_0(apiDoc);\n }\n\n throw new Error(\n `Unsupported API document. Plugin supports Swagger 1.2, 2.0, 3.0(Open API 3.0)`,\n );\n }\n\n async convertSwagger1_2To2_0(swaggerDoc: any): Promise<any> {\n try {\n const result = SwaggerConverter.convert(swaggerDoc, {});\n return result;\n } catch (error) {\n console.error('Error converting Swagger 1.2 to Swagger 2.0:', error);\n throw error;\n }\n }\n\n private async convertSwagger2_0ToOpenAPI3_0(swaggerDoc: any): Promise<any> {\n try {\n const result = await Swagger2OpenAPI.convertObj(swaggerDoc, {\n patch: true, // patch: true helps to fix minor issues\n warnOnly: true, // Do not throw on non-patchable errors\n });\n return result.openapi;\n } catch (error) {\n console.error('Error converting Swagger 2.0 to OpenAPI 3.0:', error);\n throw error;\n }\n }\n}\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n SchedulerServiceTaskRunner,\n SchedulerService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n ANNOTATION_LOCATION,\n ANNOTATION_ORIGIN_LOCATION,\n ApiEntity,\n Entity,\n} from '@backstage/catalog-model';\n\nimport type { Config } from '@backstage/config';\nimport { InputError, isError, NotFoundError } from '@backstage/errors';\n\nimport {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\n\nimport {\n getProxyConfig,\n listApiDocs,\n listServices,\n} from '../clients/ThreeScaleAPIConnector';\nimport type {\n APIDocElement,\n APIDocs,\n Proxy,\n ServiceElement,\n Services,\n} from '../clients/types';\nimport { readThreeScaleApiEntityConfigs } from './config';\nimport { isNonEmptyArray, NonEmptyArray, ThreeScaleConfig } from './types';\nimport {\n isOpenAPI3_0,\n isSwagger1_2,\n isSwagger2_0,\n OpenAPIMergerAndConverter,\n} from './open-api-merger-converter';\nimport { Swagger } from 'atlassian-openapi';\n\nexport class ThreeScaleApiEntityProvider implements EntityProvider {\n private static SERVICES_FETCH_SIZE: number = 500;\n private readonly env: string;\n private readonly baseUrl: string;\n private readonly accessToken: string;\n private readonly logger: LoggerService;\n private readonly scheduleFn: () => Promise<void>;\n private readonly openApiMerger: OpenAPIMergerAndConverter;\n private connection?: EntityProviderConnection;\n\n static fromConfig(\n deps: {\n config: Config;\n logger: LoggerService;\n },\n\n options: {\n schedule: SchedulerServiceTaskRunner;\n scheduler: SchedulerService;\n },\n ): ThreeScaleApiEntityProvider[] {\n const providerConfigs = readThreeScaleApiEntityConfigs(deps.config);\n\n if (!options.schedule && !options.scheduler) {\n throw new Error('Either schedule or scheduler must be provided.');\n }\n\n return providerConfigs.map(providerConfig => {\n if (!options.schedule && !providerConfig.schedule) {\n throw new InputError(\n `No schedule provided via config for ThreeScaleApiEntityProvider:${providerConfig.id}.`,\n );\n }\n\n let taskRunner;\n\n if (options.scheduler && providerConfig.schedule) {\n // Create a scheduled task runner using the provided scheduler and schedule configuration\n taskRunner = options.scheduler.createScheduledTaskRunner(\n providerConfig.schedule,\n );\n } else if (options.schedule) {\n // Use the provided schedule directly\n taskRunner = options.schedule;\n } else {\n // Handle the case where both options.schedule and options.scheduler are missing\n throw new Error('Neither schedule nor scheduler is provided.');\n }\n\n return new ThreeScaleApiEntityProvider(\n providerConfig,\n deps.logger,\n taskRunner,\n );\n });\n }\n\n private constructor(\n config: ThreeScaleConfig,\n logger: LoggerService,\n taskRunner: SchedulerServiceTaskRunner,\n ) {\n this.env = config.id;\n this.baseUrl = config.baseUrl;\n this.accessToken = config.accessToken;\n this.logger = logger.child({\n target: this.getProviderName(),\n });\n\n this.scheduleFn = this.createScheduleFn(taskRunner);\n this.openApiMerger = new OpenAPIMergerAndConverter();\n }\n\n private createScheduleFn(\n taskRunner: SchedulerServiceTaskRunner,\n ): () => Promise<void> {\n return async () => {\n const taskId = `${this.getProviderName()}:run`;\n return taskRunner.run({\n id: taskId,\n fn: async () => {\n try {\n await this.run();\n } catch (error: any) {\n if (isError(error)) {\n // Ensure that we don't log any sensitive internal data:\n this.logger.error(\n `Error while syncing 3scale API from ${this.baseUrl}`,\n {\n // Default Error properties:\n name: error.name,\n message: error.message,\n stack: error.stack,\n // Additional status code if available:\n status: (error.response as { status?: string })?.status,\n },\n );\n }\n }\n },\n });\n };\n }\n\n getProviderName(): string {\n return `ThreeScaleApiEntityProvider:${this.env}`;\n }\n\n async connect(connection: EntityProviderConnection): Promise<void> {\n this.connection = connection;\n await this.scheduleFn();\n }\n\n async run(): Promise<void> {\n if (!this.connection) {\n throw new NotFoundError('Not initialized');\n }\n\n this.logger.info(`Discovering ApiEntities from 3scale ${this.baseUrl}`);\n\n const entities: Entity[] = [];\n\n let page: number = 0;\n let services: Services;\n let apiDocs: APIDocs;\n let fetchServices: boolean = true;\n while (fetchServices) {\n services = await listServices(\n this.baseUrl,\n this.accessToken,\n page,\n ThreeScaleApiEntityProvider.SERVICES_FETCH_SIZE,\n );\n apiDocs = await listApiDocs(this.baseUrl, this.accessToken);\n for (const element of services.services) {\n const service = element;\n this.logger.debug(`Find service ${service.service.name}`);\n\n const docs = apiDocs.api_docs.filter(\n obj => obj.api_doc.service_id === service.service.id,\n );\n const proxy = await getProxyConfig(\n this.baseUrl,\n this.accessToken,\n service.service.id,\n );\n if (isNonEmptyArray(docs)) {\n this.logger.info(JSON.stringify(docs));\n const apiEntity: ApiEntity = await this.buildApiEntityFromService(\n service,\n docs,\n proxy,\n );\n entities.push(apiEntity);\n this.logger.debug(`Discovered ApiEntity ${service.service.name}`);\n }\n }\n\n if (\n services.services.length <\n ThreeScaleApiEntityProvider.SERVICES_FETCH_SIZE\n ) {\n fetchServices = false;\n }\n page++;\n }\n\n this.logger.info(`Applying the mutation with ${entities.length} entities`);\n\n await this.connection.applyMutation({\n type: 'full',\n entities: entities.map(entity => ({\n entity,\n locationKey: this.getProviderName(),\n })),\n });\n }\n\n private async buildApiEntityFromService(\n service: ServiceElement,\n apiDocs: NonEmptyArray<APIDocElement>,\n proxy: Proxy,\n ): Promise<ApiEntity> {\n const location = `url:${this.baseUrl}/apiconfig/services/${service.service.id}`;\n const serviceDescription = service.service.description || '';\n let entityDescription: string | undefined;\n\n const docs = apiDocs.map(doc => JSON.parse(doc.api_doc.body));\n\n let swaggerDocJSON;\n if (docs.length > 1) {\n // convert all docs to openapi 3.0 and merge them\n let mergedDescription = `[Merged ${docs.length} API docs]`;\n let mergedTitle = mergedDescription;\n const convertedDocs: Swagger.SwaggerV3[] = [];\n for (const doc of docs) {\n const convertedDoc = await this.openApiMerger.convertAPIDocToOpenAPI3(\n doc,\n );\n convertedDocs.push(convertedDoc);\n mergedDescription = getDocInfo(convertedDoc)?.description\n ? `${mergedDescription} ${getDocInfo(convertedDoc)?.description}`\n : mergedDescription;\n mergedTitle = getDocInfo(convertedDoc)?.title\n ? `${mergedTitle} ${getDocInfo(convertedDoc)?.title}`\n : mergedTitle;\n }\n if (isNonEmptyArray(convertedDocs)) {\n swaggerDocJSON = await this.openApiMerger.mergeOpenAPI3Docs(\n convertedDocs,\n );\n swaggerDocJSON.info.description = mergedDescription;\n swaggerDocJSON.info.title = mergedTitle;\n entityDescription = mergedDescription;\n }\n }\n\n if (docs.length === 1) {\n swaggerDocJSON = docs[0];\n\n const spec = JSON.parse(apiDocs[0].api_doc.body);\n if (isSwagger1_2(spec)) {\n // Backstage UI can render only openapi 3.0 or swagger 2.0. That's why we need to convert swagger 1.2 to swagger 2.0.\n swaggerDocJSON = await this.openApiMerger.convertSwagger1_2To2_0(spec);\n }\n entityDescription = getDocInfo(spec)?.description;\n }\n\n return {\n kind: 'API',\n apiVersion: 'backstage.io/v1alpha1',\n metadata: {\n annotations: {\n [ANNOTATION_LOCATION]: location,\n [ANNOTATION_ORIGIN_LOCATION]: location,\n },\n // TODO: add tenant name\n name: `${service.service.system_name}`,\n description: entityDescription || serviceDescription,\n // TODO: add labels\n // labels: this.getApiEntityLabels(service),\n links: [\n {\n url: `${this.baseUrl}/apiconfig/services/${service.service.id}`,\n title: '3scale Overview',\n },\n {\n url: `${proxy.proxy.sandbox_endpoint}`,\n title: 'Staging Apicast Endpoint',\n },\n {\n url: `${proxy.proxy.endpoint}`,\n title: 'Production Apicast Endpoint',\n },\n ],\n },\n spec: {\n type: 'openapi',\n lifecycle: this.env,\n system: '3scale',\n owner: '3scale',\n definition: JSON.stringify(swaggerDocJSON, null, 2),\n },\n };\n }\n}\n\nfunction getDocInfo(\n spec: any,\n): { description: string; title: string } | undefined {\n if (isSwagger2_0(spec) || isOpenAPI3_0(spec)) {\n return spec.info;\n }\n\n // swagger 1.2 spec doc defined by single file doesn't have description field\n return undefined;\n}\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';\n\nimport { ThreeScaleApiEntityProvider } from './providers';\n\nexport const catalogModule3ScaleEntityProvider = createBackendModule({\n moduleId: 'catalog-backend-module-3scale',\n pluginId: 'catalog',\n register(env) {\n env.registerInit({\n deps: {\n catalog: catalogProcessingExtensionPoint,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n scheduler: coreServices.scheduler,\n },\n async init({ catalog, config, logger, scheduler }) {\n catalog.addEntityProvider(\n ThreeScaleApiEntityProvider.fromConfig(\n { config, logger },\n {\n scheduler: scheduler,\n schedule: scheduler.createScheduledTaskRunner({\n frequency: { minutes: 30 },\n timeout: { minutes: 3 },\n }),\n },\n ),\n );\n },\n });\n },\n});\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig","merge","isErrorResult","SwaggerConverter","Swagger2OpenAPI","InputError","isError","NotFoundError","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION","createBackendModule","catalogProcessingExtensionPoint","coreServices"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
+
import { LoggerService, SchedulerServiceTaskRunner, SchedulerService } from '@backstage/backend-plugin-api';
|
|
3
3
|
import { Config } from '@backstage/config';
|
|
4
4
|
import { EntityProvider, EntityProviderConnection } from '@backstage/plugin-catalog-node';
|
|
5
|
-
import { BackendDynamicPluginInstaller } from '@backstage/backend-dynamic-feature-service';
|
|
6
5
|
|
|
7
6
|
interface Services {
|
|
8
7
|
services: ServiceElement[];
|
|
@@ -106,6 +105,8 @@ declare function listServices(baseUrl: string, access_token: string, page: numbe
|
|
|
106
105
|
declare function listApiDocs(baseUrl: string, access_token: string): Promise<APIDocs>;
|
|
107
106
|
declare function getProxyConfig(baseUrl: string, access_token: string, service_id: number): Promise<Proxy>;
|
|
108
107
|
|
|
108
|
+
declare const catalogModule3ScaleEntityProvider: _backstage_backend_plugin_api.BackendFeature;
|
|
109
|
+
|
|
109
110
|
declare class ThreeScaleApiEntityProvider implements EntityProvider {
|
|
110
111
|
private static SERVICES_FETCH_SIZE;
|
|
111
112
|
private readonly env;
|
|
@@ -113,11 +114,14 @@ declare class ThreeScaleApiEntityProvider implements EntityProvider {
|
|
|
113
114
|
private readonly accessToken;
|
|
114
115
|
private readonly logger;
|
|
115
116
|
private readonly scheduleFn;
|
|
117
|
+
private readonly openApiMerger;
|
|
116
118
|
private connection?;
|
|
117
|
-
static fromConfig(
|
|
119
|
+
static fromConfig(deps: {
|
|
120
|
+
config: Config;
|
|
118
121
|
logger: LoggerService;
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
}, options: {
|
|
123
|
+
schedule: SchedulerServiceTaskRunner;
|
|
124
|
+
scheduler: SchedulerService;
|
|
121
125
|
}): ThreeScaleApiEntityProvider[];
|
|
122
126
|
private constructor();
|
|
123
127
|
private createScheduleFn;
|
|
@@ -127,6 +131,4 @@ declare class ThreeScaleApiEntityProvider implements EntityProvider {
|
|
|
127
131
|
private buildApiEntityFromService;
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
export { ThreeScaleApiEntityProvider, dynamicPluginInstaller, getProxyConfig, listApiDocs, listServices };
|
|
134
|
+
export { ThreeScaleApiEntityProvider, catalogModule3ScaleEntityProvider as default, getProxyConfig, listApiDocs, listServices };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# 3scale Backstage provider
|
|
2
|
+
|
|
3
|
+
The 3scale Backstage provider plugin synchronizes the 3scale content into the [Backstage](https://backstage.io/) catalog.
|
|
4
|
+
|
|
5
|
+
## For administrators
|
|
6
|
+
|
|
7
|
+
### Installation
|
|
8
|
+
|
|
9
|
+
Run the following command to install the 3scale Backstage provider plugin:
|
|
10
|
+
|
|
11
|
+
```console
|
|
12
|
+
yarn workspace backend add @backstage-community/plugin-3scale-backend
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Configuration
|
|
16
|
+
|
|
17
|
+
3scale Backstage provider allows configuration of one or multiple providers using the `app-config.yaml` configuration file of Backstage.
|
|
18
|
+
|
|
19
|
+
#### New Backend Procedure
|
|
20
|
+
|
|
21
|
+
1. Use a `threeScaleApiEntity` marker to start configuring the `app-config.yaml` file of Backstage:
|
|
22
|
+
|
|
23
|
+
```yaml title="app-config.yaml"
|
|
24
|
+
catalog:
|
|
25
|
+
providers:
|
|
26
|
+
threeScaleApiEntity:
|
|
27
|
+
dev:
|
|
28
|
+
baseUrl: https://<TENANT>-admin.3scale.net
|
|
29
|
+
accessToken: <ACCESS_TOKEN>
|
|
30
|
+
schedule: # optional; same options as in TaskScheduleDefinition
|
|
31
|
+
# supports cron, ISO duration, "human duration" as used in code
|
|
32
|
+
frequency: { minutes: 30 }
|
|
33
|
+
# supports ISO duration, "human duration" as used in code
|
|
34
|
+
timeout: { minutes: 3 }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**NOTE**
|
|
38
|
+
Make sure to configure the schedule inside the `app-config.yaml` file. The default schedule is a frequency of 30 minutes and a timeout of 3 minutes.
|
|
39
|
+
|
|
40
|
+
2. Add the following code to the `packages/backend/src/index.ts` file:
|
|
41
|
+
|
|
42
|
+
```ts title="packages/backend/src/index.ts"
|
|
43
|
+
const backend = createBackend();
|
|
44
|
+
|
|
45
|
+
/* highlight-add-next-line */
|
|
46
|
+
backend.add(import('@backstage-community/plugin-3scale-backend'));
|
|
47
|
+
|
|
48
|
+
backend.start();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Troubleshooting
|
|
52
|
+
|
|
53
|
+
When you start your Backstage application, you can see some log lines as follows:
|
|
54
|
+
|
|
55
|
+
```log
|
|
56
|
+
[1] 2023-02-13T15:26:09.356Z catalog info Discovered ApiEntity API type=plugin target=ThreeScaleApiEntityProvider:dev
|
|
57
|
+
[1] 2023-02-13T15:26:09.423Z catalog info Discovered ApiEntity Red Hat Event (DEV, v1.2.0) type=plugin target=ThreeScaleApiEntityProvider:dev
|
|
58
|
+
[1] 2023-02-13T15:26:09.620Z catalog info Discovered ApiEntity Red Hat Event (TEST, v1.1.0) type=plugin target=ThreeScaleApiEntityProvider:dev
|
|
59
|
+
[1] 2023-02-13T15:26:09.819Z catalog info Discovered ApiEntity Red Hat Event (PROD, v1.1.0) type=plugin target=ThreeScaleApiEntityProvider:dev
|
|
60
|
+
[1] 2023-02-13T15:26:09.819Z catalog info Applying the mutation with 3 entities type=plugin target=ThreeScaleApiEntityProvider:dev
|
|
61
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
catalog:
|
|
2
|
+
providers:
|
|
3
|
+
threeScaleApiEntity:
|
|
4
|
+
dev:
|
|
5
|
+
baseUrl: '${THREESCALE_BASE_URL}' # https://<TENANT>-admin.3scale.net
|
|
6
|
+
accessToken: '${THREESCALE_ACCESS_TOKEN}'
|
|
7
|
+
schedule: # optional; same options as in TaskScheduleDefinition
|
|
8
|
+
# supports cron, ISO duration, "human duration" as used in code
|
|
9
|
+
frequency: { minutes: 1 }
|
|
10
|
+
# supports ISO duration, "human duration" as used in code
|
|
11
|
+
timeout: { minutes: 1 }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 The Backstage Authors
|
|
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
|
+
|
|
17
|
+
import type { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api';
|
|
18
|
+
|
|
19
|
+
export interface Config {
|
|
20
|
+
catalog?: {
|
|
21
|
+
providers?: {
|
|
22
|
+
threeScaleApiEntity?: {
|
|
23
|
+
[key: string]: {
|
|
24
|
+
/**
|
|
25
|
+
* ThreeScaleConfig
|
|
26
|
+
*/
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
/** @visibility secret */
|
|
29
|
+
accessToken: string;
|
|
30
|
+
systemLabel?: string;
|
|
31
|
+
ownerLabel?: string;
|
|
32
|
+
addLabels?: boolean;
|
|
33
|
+
schedule?: SchedulerServiceTaskScheduleDefinitionConfig;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@backstage-community/plugin-3scale-backend-dynamic",
|
|
3
|
+
"version": "1.8.5",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"main": "./dist/index.cjs.js",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"backstage": {
|
|
11
|
+
"role": "backend-plugin-module",
|
|
12
|
+
"supported-versions": "1.28.4",
|
|
13
|
+
"pluginId": "3scale",
|
|
14
|
+
"pluginPackage": "@backstage-community/plugin-3scale-backend"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"require": "./dist/index.cjs.js",
|
|
19
|
+
"default": "./dist/index.cjs.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"config.d.ts",
|
|
26
|
+
"app-config.yaml"
|
|
27
|
+
],
|
|
28
|
+
"configSchema": "config.d.ts",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/backstage/community-plugins",
|
|
32
|
+
"directory": "workspaces/3scale/plugins/3scale-backend"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"support:tech-preview",
|
|
36
|
+
"lifecycle:active",
|
|
37
|
+
"backstage",
|
|
38
|
+
"plugin"
|
|
39
|
+
],
|
|
40
|
+
"homepage": "https://red.ht/rhdh",
|
|
41
|
+
"bugs": "https://github.com/backstage/community-plugins/issues",
|
|
42
|
+
"author": "Red Hat",
|
|
43
|
+
"bundleDependencies": true,
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@backstage/backend-plugin-api": "^1.0.0",
|
|
46
|
+
"@backstage/catalog-model": "^1.7.0",
|
|
47
|
+
"@backstage/errors": "^1.2.4",
|
|
48
|
+
"@backstage/plugin-catalog-node": "^1.13.0"
|
|
49
|
+
},
|
|
50
|
+
"overrides": {
|
|
51
|
+
"@aws-sdk/util-utf8-browser": {
|
|
52
|
+
"@smithy/util-utf8": "^2.0.0"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"resolutions": {
|
|
56
|
+
"@aws-sdk/util-utf8-browser": "npm:@smithy/util-utf8@~2"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# This file is generated by running "yarn install" inside your project.
|
|
2
|
+
# Manual changes might be lost - proceed with caution!
|
|
3
|
+
|
|
4
|
+
__metadata:
|
|
5
|
+
version: 6
|
|
6
|
+
|
|
7
|
+
"@backstage-community/plugin-3scale-backend-dynamic@workspace:.":
|
|
8
|
+
version: 0.0.0-use.local
|
|
9
|
+
resolution: "@backstage-community/plugin-3scale-backend-dynamic@workspace:."
|
|
10
|
+
peerDependencies:
|
|
11
|
+
"@backstage/backend-plugin-api": ^1.0.0
|
|
12
|
+
"@backstage/catalog-model": ^1.7.0
|
|
13
|
+
"@backstage/errors": ^1.2.4
|
|
14
|
+
"@backstage/plugin-catalog-node": ^1.13.0
|
|
15
|
+
languageName: unknown
|
|
16
|
+
linkType: soft
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage-community/plugin-3scale-backend",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./dist/index.cjs.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -19,11 +19,6 @@
|
|
|
19
19
|
"types": "./dist/index.d.ts",
|
|
20
20
|
"default": "./dist/index.cjs.js"
|
|
21
21
|
},
|
|
22
|
-
"./alpha": {
|
|
23
|
-
"require": "./dist/alpha.cjs.js",
|
|
24
|
-
"types": "./dist/alpha.d.ts",
|
|
25
|
-
"default": "./dist/alpha.cjs.js"
|
|
26
|
-
},
|
|
27
22
|
"./package.json": "./package.json"
|
|
28
23
|
},
|
|
29
24
|
"scripts": {
|
|
@@ -35,22 +30,27 @@
|
|
|
35
30
|
"postversion": "yarn run export-dynamic",
|
|
36
31
|
"prepack": "backstage-cli package prepack",
|
|
37
32
|
"start": "backstage-cli package start",
|
|
38
|
-
"test": "backstage-cli package test --passWithNoTests --coverage"
|
|
39
|
-
"tsc": "tsc"
|
|
33
|
+
"test": "backstage-cli package test --passWithNoTests --coverage"
|
|
40
34
|
},
|
|
41
35
|
"dependencies": {
|
|
42
|
-
"@backstage/backend-common": "^0.25.0",
|
|
43
|
-
"@backstage/backend-dynamic-feature-service": "^0.4.0",
|
|
44
36
|
"@backstage/backend-plugin-api": "^1.0.0",
|
|
45
|
-
"@backstage/backend-tasks": "^0.6.1",
|
|
46
37
|
"@backstage/catalog-model": "^1.7.0",
|
|
47
|
-
"@backstage/
|
|
48
|
-
"@backstage/plugin-catalog-node": "^1.13.0"
|
|
38
|
+
"@backstage/errors": "^1.2.4",
|
|
39
|
+
"@backstage/plugin-catalog-node": "^1.13.0",
|
|
40
|
+
"atlassian-openapi": "^1.0.19",
|
|
41
|
+
"openapi-merge": "^1.3.3",
|
|
42
|
+
"swagger-converter": "2.1.0",
|
|
43
|
+
"swagger2openapi": "^7.0.4"
|
|
49
44
|
},
|
|
50
45
|
"devDependencies": {
|
|
46
|
+
"@backstage/backend-defaults": "^0.5.0",
|
|
47
|
+
"@backstage/backend-test-utils": "0.4.4",
|
|
51
48
|
"@backstage/cli": "^0.27.1",
|
|
49
|
+
"@backstage/config": "^1.2.0",
|
|
50
|
+
"@backstage/plugin-catalog-backend": "^1.26.0",
|
|
52
51
|
"@janus-idp/cli": "1.13.1",
|
|
53
52
|
"@types/supertest": "6.0.2",
|
|
53
|
+
"@types/swagger2openapi": "^7.0.4",
|
|
54
54
|
"msw": "1.3.3",
|
|
55
55
|
"supertest": "7.0.0"
|
|
56
56
|
},
|
|
@@ -59,9 +59,7 @@
|
|
|
59
59
|
"config.d.ts",
|
|
60
60
|
"dist-dynamic/*.*",
|
|
61
61
|
"dist-dynamic/dist/**",
|
|
62
|
-
"
|
|
63
|
-
"app-config.yaml",
|
|
64
|
-
"alpha"
|
|
62
|
+
"app-config.yaml"
|
|
65
63
|
],
|
|
66
64
|
"configSchema": "config.d.ts",
|
|
67
65
|
"repository": {
|
package/alpha/package.json
DELETED
package/dist/alpha.cjs.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
6
|
-
var alpha = require('@backstage/plugin-catalog-node/alpha');
|
|
7
|
-
var ThreeScaleApiEntityProvider = require('./cjs/ThreeScaleApiEntityProvider-B3TyzFXk.cjs.js');
|
|
8
|
-
require('@backstage/catalog-model');
|
|
9
|
-
require('@backstage/backend-tasks');
|
|
10
|
-
|
|
11
|
-
const catalogModule3ScaleEntityProvider = backendPluginApi.createBackendModule({
|
|
12
|
-
moduleId: "catalog-backend-module-3scale",
|
|
13
|
-
pluginId: "catalog",
|
|
14
|
-
register(env) {
|
|
15
|
-
env.registerInit({
|
|
16
|
-
deps: {
|
|
17
|
-
catalog: alpha.catalogProcessingExtensionPoint,
|
|
18
|
-
config: backendPluginApi.coreServices.rootConfig,
|
|
19
|
-
logger: backendPluginApi.coreServices.logger,
|
|
20
|
-
scheduler: backendPluginApi.coreServices.scheduler
|
|
21
|
-
},
|
|
22
|
-
async init({ catalog, config, logger, scheduler }) {
|
|
23
|
-
catalog.addEntityProvider(
|
|
24
|
-
ThreeScaleApiEntityProvider.ThreeScaleApiEntityProvider.fromConfig(config, {
|
|
25
|
-
logger,
|
|
26
|
-
scheduler,
|
|
27
|
-
schedule: scheduler.createScheduledTaskRunner({
|
|
28
|
-
frequency: { minutes: 30 },
|
|
29
|
-
timeout: { minutes: 3 }
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
exports.default = catalogModule3ScaleEntityProvider;
|
|
39
|
-
//# sourceMappingURL=alpha.cjs.js.map
|
package/dist/alpha.cjs.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"alpha.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright 2024 The Janus IDP Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';\n\nimport { ThreeScaleApiEntityProvider } from './providers';\n\nexport const catalogModule3ScaleEntityProvider = createBackendModule({\n moduleId: 'catalog-backend-module-3scale',\n pluginId: 'catalog',\n register(env) {\n env.registerInit({\n deps: {\n catalog: catalogProcessingExtensionPoint,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n scheduler: coreServices.scheduler,\n },\n async init({ catalog, config, logger, scheduler }) {\n catalog.addEntityProvider(\n ThreeScaleApiEntityProvider.fromConfig(config, {\n logger,\n scheduler: scheduler,\n schedule: scheduler.createScheduledTaskRunner({\n frequency: { minutes: 30 },\n timeout: { minutes: 3 },\n }),\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendModule","catalogProcessingExtensionPoint","coreServices","ThreeScaleApiEntityProvider"],"mappings":";;;;;;;;;;AAuBO,MAAM,oCAAoCA,oCAAoB,CAAA;AAAA,EACnE,QAAU,EAAA,+BAAA;AAAA,EACV,QAAU,EAAA,SAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,OAAS,EAAAC,qCAAA;AAAA,QACT,QAAQC,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,OAC1B;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,SAAS,MAAQ,EAAA,MAAA,EAAQ,WAAa,EAAA;AACjD,QAAQ,OAAA,CAAA,iBAAA;AAAA,UACNC,uDAAA,CAA4B,WAAW,MAAQ,EAAA;AAAA,YAC7C,MAAA;AAAA,YACA,SAAA;AAAA,YACA,QAAA,EAAU,UAAU,yBAA0B,CAAA;AAAA,cAC5C,SAAA,EAAW,EAAE,OAAA,EAAS,EAAG,EAAA;AAAA,cACzB,OAAA,EAAS,EAAE,OAAA,EAAS,CAAE,EAAA;AAAA,aACvB,CAAA;AAAA,WACF,CAAA;AAAA,SACH,CAAA;AAAA,OACF;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC;;;;"}
|
package/dist/alpha.d.ts
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var catalogModel = require('@backstage/catalog-model');
|
|
4
|
-
var backendTasks = require('@backstage/backend-tasks');
|
|
5
|
-
|
|
6
|
-
function listServices(baseUrl, access_token, page, size) {
|
|
7
|
-
return fetch(
|
|
8
|
-
`${baseUrl}/admin/api/services.json?access_token=${access_token}&page=${page}&size=${size}`
|
|
9
|
-
).then((response) => {
|
|
10
|
-
if (!response.ok) {
|
|
11
|
-
throw new Error(response.statusText);
|
|
12
|
-
}
|
|
13
|
-
return response.json();
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
function listApiDocs(baseUrl, access_token) {
|
|
17
|
-
return fetch(
|
|
18
|
-
`${baseUrl}/admin/api/active_docs.json?access_token=${access_token}`
|
|
19
|
-
).then((response) => {
|
|
20
|
-
if (!response.ok) {
|
|
21
|
-
throw new Error(response.statusText);
|
|
22
|
-
}
|
|
23
|
-
return response.json();
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
function getProxyConfig(baseUrl, access_token, service_id) {
|
|
27
|
-
return fetch(
|
|
28
|
-
`${baseUrl}/admin/api/services/${service_id}/proxy.json?access_token=${access_token}`
|
|
29
|
-
).then((response) => {
|
|
30
|
-
if (!response.ok) {
|
|
31
|
-
throw new Error(response.statusText);
|
|
32
|
-
}
|
|
33
|
-
return response.json();
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function readThreeScaleApiEntityConfigs(config) {
|
|
38
|
-
const providerConfigs = config.getOptionalConfig(
|
|
39
|
-
"catalog.providers.threeScaleApiEntity"
|
|
40
|
-
);
|
|
41
|
-
if (!providerConfigs) {
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
return providerConfigs.keys().map(
|
|
45
|
-
(id) => readThreeScaleApiEntityConfig(id, providerConfigs.getConfig(id))
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
function readThreeScaleApiEntityConfig(id, config) {
|
|
49
|
-
const baseUrl = config.getString("baseUrl");
|
|
50
|
-
const accessToken = config.getString("accessToken");
|
|
51
|
-
const systemLabel = config.getOptionalString("systemLabel");
|
|
52
|
-
const ownerLabel = config.getOptionalString("ownerLabel");
|
|
53
|
-
const addLabels = config.getOptionalBoolean("addLabels") || true;
|
|
54
|
-
const schedule = config.has("schedule") ? backendTasks.readTaskScheduleDefinitionFromConfig(config.getConfig("schedule")) : void 0;
|
|
55
|
-
return {
|
|
56
|
-
id,
|
|
57
|
-
baseUrl,
|
|
58
|
-
accessToken,
|
|
59
|
-
systemLabel,
|
|
60
|
-
ownerLabel,
|
|
61
|
-
addLabels,
|
|
62
|
-
schedule
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
class ThreeScaleApiEntityProvider {
|
|
67
|
-
static SERVICES_FETCH_SIZE = 500;
|
|
68
|
-
env;
|
|
69
|
-
baseUrl;
|
|
70
|
-
accessToken;
|
|
71
|
-
logger;
|
|
72
|
-
scheduleFn;
|
|
73
|
-
connection;
|
|
74
|
-
static fromConfig(configRoot, options) {
|
|
75
|
-
const providerConfigs = readThreeScaleApiEntityConfigs(configRoot);
|
|
76
|
-
if (!options.schedule && !options.scheduler) {
|
|
77
|
-
throw new Error("Either schedule or scheduler must be provided.");
|
|
78
|
-
}
|
|
79
|
-
return providerConfigs.map((providerConfig) => {
|
|
80
|
-
if (!options.schedule && !providerConfig.schedule) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
`No schedule provided neither via code nor config for ThreeScaleApiEntityProvider:${providerConfig.id}.`
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
let taskRunner;
|
|
86
|
-
if (options.scheduler && providerConfig.schedule) {
|
|
87
|
-
taskRunner = options.scheduler.createScheduledTaskRunner(
|
|
88
|
-
providerConfig.schedule
|
|
89
|
-
);
|
|
90
|
-
} else if (options.schedule) {
|
|
91
|
-
taskRunner = options.schedule;
|
|
92
|
-
} else {
|
|
93
|
-
throw new Error("Neither schedule nor scheduler is provided.");
|
|
94
|
-
}
|
|
95
|
-
return new ThreeScaleApiEntityProvider(
|
|
96
|
-
providerConfig,
|
|
97
|
-
options.logger,
|
|
98
|
-
taskRunner
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
constructor(config, logger, taskRunner) {
|
|
103
|
-
this.env = config.id;
|
|
104
|
-
this.baseUrl = config.baseUrl;
|
|
105
|
-
this.accessToken = config.accessToken;
|
|
106
|
-
this.logger = logger.child({
|
|
107
|
-
target: this.getProviderName()
|
|
108
|
-
});
|
|
109
|
-
this.scheduleFn = this.createScheduleFn(taskRunner);
|
|
110
|
-
}
|
|
111
|
-
createScheduleFn(taskRunner) {
|
|
112
|
-
return async () => {
|
|
113
|
-
const taskId = `${this.getProviderName()}:run`;
|
|
114
|
-
return taskRunner.run({
|
|
115
|
-
id: taskId,
|
|
116
|
-
fn: async () => {
|
|
117
|
-
try {
|
|
118
|
-
await this.run();
|
|
119
|
-
} catch (error) {
|
|
120
|
-
this.logger.error(
|
|
121
|
-
`Error while syncing 3scale API from ${this.baseUrl}`,
|
|
122
|
-
{
|
|
123
|
-
// Default Error properties:
|
|
124
|
-
name: error.name,
|
|
125
|
-
message: error.message,
|
|
126
|
-
stack: error.stack,
|
|
127
|
-
// Additional status code if available:
|
|
128
|
-
status: error.response?.status
|
|
129
|
-
}
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
getProviderName() {
|
|
137
|
-
return `ThreeScaleApiEntityProvider:${this.env}`;
|
|
138
|
-
}
|
|
139
|
-
async connect(connection) {
|
|
140
|
-
this.connection = connection;
|
|
141
|
-
await this.scheduleFn();
|
|
142
|
-
}
|
|
143
|
-
async run() {
|
|
144
|
-
if (!this.connection) {
|
|
145
|
-
throw new Error("Not initialized");
|
|
146
|
-
}
|
|
147
|
-
this.logger.info(`Discovering ApiEntities from 3scale ${this.baseUrl}`);
|
|
148
|
-
const entities = [];
|
|
149
|
-
let page = 0;
|
|
150
|
-
let services;
|
|
151
|
-
let apiDocs;
|
|
152
|
-
let fetchServices = true;
|
|
153
|
-
while (fetchServices) {
|
|
154
|
-
services = await listServices(
|
|
155
|
-
this.baseUrl,
|
|
156
|
-
this.accessToken,
|
|
157
|
-
page,
|
|
158
|
-
ThreeScaleApiEntityProvider.SERVICES_FETCH_SIZE
|
|
159
|
-
);
|
|
160
|
-
apiDocs = await listApiDocs(this.baseUrl, this.accessToken);
|
|
161
|
-
for (const element of services.services) {
|
|
162
|
-
const service = element;
|
|
163
|
-
this.logger.debug(`Find service ${service.service.name}`);
|
|
164
|
-
const apiDoc = apiDocs.api_docs.find((obj) => {
|
|
165
|
-
if (obj.api_doc.service_id !== void 0) {
|
|
166
|
-
return obj.api_doc.service_id === service.service.id;
|
|
167
|
-
}
|
|
168
|
-
return false;
|
|
169
|
-
});
|
|
170
|
-
const proxy = await getProxyConfig(
|
|
171
|
-
this.baseUrl,
|
|
172
|
-
this.accessToken,
|
|
173
|
-
service.service.id
|
|
174
|
-
);
|
|
175
|
-
if (apiDoc !== void 0) {
|
|
176
|
-
this.logger.info(JSON.stringify(apiDoc));
|
|
177
|
-
const apiEntity = this.buildApiEntityFromService(
|
|
178
|
-
service,
|
|
179
|
-
apiDoc,
|
|
180
|
-
proxy
|
|
181
|
-
);
|
|
182
|
-
entities.push(apiEntity);
|
|
183
|
-
this.logger.debug(`Discovered ApiEntity ${service.service.name}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (services.services.length < ThreeScaleApiEntityProvider.SERVICES_FETCH_SIZE) {
|
|
187
|
-
fetchServices = false;
|
|
188
|
-
}
|
|
189
|
-
page++;
|
|
190
|
-
}
|
|
191
|
-
this.logger.info(`Applying the mutation with ${entities.length} entities`);
|
|
192
|
-
await this.connection.applyMutation({
|
|
193
|
-
type: "full",
|
|
194
|
-
entities: entities.map((entity) => ({
|
|
195
|
-
entity,
|
|
196
|
-
locationKey: this.getProviderName()
|
|
197
|
-
}))
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
buildApiEntityFromService(service, apiDoc, proxy) {
|
|
201
|
-
const location = `url:${this.baseUrl}/apiconfig/services/${service.service.id}`;
|
|
202
|
-
const spec = JSON.parse(apiDoc.api_doc.body);
|
|
203
|
-
return {
|
|
204
|
-
kind: "API",
|
|
205
|
-
apiVersion: "backstage.io/v1alpha1",
|
|
206
|
-
metadata: {
|
|
207
|
-
annotations: {
|
|
208
|
-
[catalogModel.ANNOTATION_LOCATION]: location,
|
|
209
|
-
[catalogModel.ANNOTATION_ORIGIN_LOCATION]: location
|
|
210
|
-
},
|
|
211
|
-
// TODO: add tenant name
|
|
212
|
-
name: `${service.service.system_name}`,
|
|
213
|
-
description: spec.info.description || `Version: ${service.service.description}`,
|
|
214
|
-
// TODO: add labels
|
|
215
|
-
// labels: this.getApiEntityLabels(service),
|
|
216
|
-
links: [
|
|
217
|
-
{
|
|
218
|
-
url: `${this.baseUrl}/apiconfig/services/${service.service.id}`,
|
|
219
|
-
title: "3scale Overview"
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
url: `${proxy.proxy.sandbox_endpoint}`,
|
|
223
|
-
title: "Staging Apicast Endpoint"
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
url: `${proxy.proxy.endpoint}`,
|
|
227
|
-
title: "Production Apicast Endpoint"
|
|
228
|
-
}
|
|
229
|
-
]
|
|
230
|
-
},
|
|
231
|
-
spec: {
|
|
232
|
-
type: "openapi",
|
|
233
|
-
lifecycle: this.env,
|
|
234
|
-
system: "3scale",
|
|
235
|
-
owner: "3scale",
|
|
236
|
-
definition: apiDoc.api_doc.body
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
exports.ThreeScaleApiEntityProvider = ThreeScaleApiEntityProvider;
|
|
243
|
-
exports.getProxyConfig = getProxyConfig;
|
|
244
|
-
exports.listApiDocs = listApiDocs;
|
|
245
|
-
exports.listServices = listServices;
|
|
246
|
-
//# sourceMappingURL=ThreeScaleApiEntityProvider-B3TyzFXk.cjs.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ThreeScaleApiEntityProvider-B3TyzFXk.cjs.js","sources":["../../src/clients/ThreeScaleAPIConnector.ts","../../src/providers/config.ts","../../src/providers/ThreeScaleApiEntityProvider.ts"],"sourcesContent":["import { APIDocs, Proxy, Services } from './types';\n\nexport function listServices(\n baseUrl: string,\n access_token: string,\n page: number,\n size: number,\n): Promise<Services> {\n return fetch(\n `${baseUrl}/admin/api/services.json?access_token=${access_token}&page=${page}&size=${size}`,\n ).then(response => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.json() as Promise<Services>;\n });\n}\n\nexport function listApiDocs(\n baseUrl: string,\n access_token: string,\n): Promise<APIDocs> {\n return fetch(\n `${baseUrl}/admin/api/active_docs.json?access_token=${access_token}`,\n ).then(response => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.json() as Promise<APIDocs>;\n });\n}\n\nexport function getProxyConfig(\n baseUrl: string,\n access_token: string,\n service_id: number,\n): Promise<Proxy> {\n return fetch(\n `${baseUrl}/admin/api/services/${service_id}/proxy.json?access_token=${access_token}`,\n ).then(response => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.json() as Promise<Proxy>;\n });\n}\n","import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks';\nimport { Config } from '@backstage/config';\n\nimport { ThreeScaleConfig } from './types';\n\nexport function readThreeScaleApiEntityConfigs(\n config: Config,\n): ThreeScaleConfig[] {\n const providerConfigs = config.getOptionalConfig(\n 'catalog.providers.threeScaleApiEntity',\n );\n if (!providerConfigs) {\n return [];\n }\n return providerConfigs\n .keys()\n .map(id =>\n readThreeScaleApiEntityConfig(id, providerConfigs.getConfig(id)),\n );\n}\n\nfunction readThreeScaleApiEntityConfig(\n id: string,\n config: Config,\n): ThreeScaleConfig {\n const baseUrl = config.getString('baseUrl');\n const accessToken = config.getString('accessToken');\n const systemLabel = config.getOptionalString('systemLabel');\n const ownerLabel = config.getOptionalString('ownerLabel');\n const addLabels = config.getOptionalBoolean('addLabels') || true;\n\n const schedule = config.has('schedule')\n ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule'))\n : undefined;\n\n return {\n id,\n baseUrl,\n accessToken,\n systemLabel,\n ownerLabel,\n addLabels,\n schedule,\n };\n}\n","import { LoggerService } from '@backstage/backend-plugin-api';\nimport { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks';\nimport {\n ANNOTATION_LOCATION,\n ANNOTATION_ORIGIN_LOCATION,\n ApiEntity,\n Entity,\n} from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\n\nimport {\n getProxyConfig,\n listApiDocs,\n listServices,\n} from '../clients/ThreeScaleAPIConnector';\nimport {\n APIDocElement,\n APIDocs,\n Proxy,\n ServiceElement,\n Services,\n} from '../clients/types';\nimport { readThreeScaleApiEntityConfigs } from './config';\nimport { ThreeScaleConfig } from './types';\n\nexport class ThreeScaleApiEntityProvider implements EntityProvider {\n private static SERVICES_FETCH_SIZE: number = 500;\n private readonly env: string;\n private readonly baseUrl: string;\n private readonly accessToken: string;\n private readonly logger: LoggerService;\n private readonly scheduleFn: () => Promise<void>;\n private connection?: EntityProviderConnection;\n\n static fromConfig(\n configRoot: Config,\n options: {\n logger: LoggerService;\n schedule?: TaskRunner;\n scheduler?: PluginTaskScheduler;\n },\n ): ThreeScaleApiEntityProvider[] {\n const providerConfigs = readThreeScaleApiEntityConfigs(configRoot);\n\n if (!options.schedule && !options.scheduler) {\n throw new Error('Either schedule or scheduler must be provided.');\n }\n\n return providerConfigs.map(providerConfig => {\n if (!options.schedule && !providerConfig.schedule) {\n throw new Error(\n `No schedule provided neither via code nor config for ThreeScaleApiEntityProvider:${providerConfig.id}.`,\n );\n }\n\n let taskRunner;\n\n if (options.scheduler && providerConfig.schedule) {\n // Create a scheduled task runner using the provided scheduler and schedule configuration\n taskRunner = options.scheduler.createScheduledTaskRunner(\n providerConfig.schedule,\n );\n } else if (options.schedule) {\n // Use the provided schedule directly\n taskRunner = options.schedule;\n } else {\n // Handle the case where both options.schedule and options.scheduler are missing\n throw new Error('Neither schedule nor scheduler is provided.');\n }\n\n return new ThreeScaleApiEntityProvider(\n providerConfig,\n options.logger,\n taskRunner,\n );\n });\n }\n\n private constructor(\n config: ThreeScaleConfig,\n logger: LoggerService,\n taskRunner: TaskRunner,\n ) {\n this.env = config.id;\n this.baseUrl = config.baseUrl;\n this.accessToken = config.accessToken;\n this.logger = logger.child({\n target: this.getProviderName(),\n });\n\n this.scheduleFn = this.createScheduleFn(taskRunner);\n }\n\n private createScheduleFn(taskRunner: TaskRunner): () => Promise<void> {\n return async () => {\n const taskId = `${this.getProviderName()}:run`;\n return taskRunner.run({\n id: taskId,\n fn: async () => {\n try {\n await this.run();\n } catch (error: any) {\n // Ensure that we don't log any sensitive internal data:\n this.logger.error(\n `Error while syncing 3scale API from ${this.baseUrl}`,\n {\n // Default Error properties:\n name: error.name,\n message: error.message,\n stack: error.stack,\n // Additional status code if available:\n status: error.response?.status,\n },\n );\n }\n },\n });\n };\n }\n\n getProviderName(): string {\n return `ThreeScaleApiEntityProvider:${this.env}`;\n }\n\n async connect(connection: EntityProviderConnection): Promise<void> {\n this.connection = connection;\n await this.scheduleFn();\n }\n\n async run(): Promise<void> {\n if (!this.connection) {\n throw new Error('Not initialized');\n }\n\n this.logger.info(`Discovering ApiEntities from 3scale ${this.baseUrl}`);\n\n const entities: Entity[] = [];\n\n let page: number = 0;\n let services: Services;\n let apiDocs: APIDocs;\n let fetchServices: boolean = true;\n while (fetchServices) {\n services = await listServices(\n this.baseUrl,\n this.accessToken,\n page,\n ThreeScaleApiEntityProvider.SERVICES_FETCH_SIZE,\n );\n apiDocs = await listApiDocs(this.baseUrl, this.accessToken);\n for (const element of services.services) {\n const service = element;\n this.logger.debug(`Find service ${service.service.name}`);\n\n // Trying to find the API Doc for the service and validate if api doc was assigned to an API.\n const apiDoc = apiDocs.api_docs.find(obj => {\n if (obj.api_doc.service_id !== undefined) {\n return obj.api_doc.service_id === service.service.id;\n }\n return false;\n });\n\n const proxy = await getProxyConfig(\n this.baseUrl,\n this.accessToken,\n service.service.id,\n );\n if (apiDoc !== undefined) {\n this.logger.info(JSON.stringify(apiDoc));\n const apiEntity: ApiEntity = this.buildApiEntityFromService(\n service,\n apiDoc,\n proxy,\n );\n entities.push(apiEntity);\n this.logger.debug(`Discovered ApiEntity ${service.service.name}`);\n }\n }\n\n if (\n services.services.length <\n ThreeScaleApiEntityProvider.SERVICES_FETCH_SIZE\n ) {\n fetchServices = false;\n }\n page++;\n }\n\n this.logger.info(`Applying the mutation with ${entities.length} entities`);\n\n await this.connection.applyMutation({\n type: 'full',\n entities: entities.map(entity => ({\n entity,\n locationKey: this.getProviderName(),\n })),\n });\n }\n\n private buildApiEntityFromService(\n service: ServiceElement,\n apiDoc: APIDocElement,\n proxy: Proxy,\n ): ApiEntity {\n const location = `url:${this.baseUrl}/apiconfig/services/${service.service.id}`;\n\n const spec = JSON.parse(apiDoc.api_doc.body);\n\n return {\n kind: 'API',\n apiVersion: 'backstage.io/v1alpha1',\n metadata: {\n annotations: {\n [ANNOTATION_LOCATION]: location,\n [ANNOTATION_ORIGIN_LOCATION]: location,\n },\n // TODO: add tenant name\n name: `${service.service.system_name}`,\n description:\n spec.info.description || `Version: ${service.service.description}`,\n // TODO: add labels\n // labels: this.getApiEntityLabels(service),\n links: [\n {\n url: `${this.baseUrl}/apiconfig/services/${service.service.id}`,\n title: '3scale Overview',\n },\n {\n url: `${proxy.proxy.sandbox_endpoint}`,\n title: 'Staging Apicast Endpoint',\n },\n {\n url: `${proxy.proxy.endpoint}`,\n title: 'Production Apicast Endpoint',\n },\n ],\n },\n spec: {\n type: 'openapi',\n lifecycle: this.env,\n system: '3scale',\n owner: '3scale',\n definition: apiDoc.api_doc.body,\n },\n };\n }\n}\n"],"names":["readTaskScheduleDefinitionFromConfig","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION"],"mappings":";;;;;AAEO,SAAS,YACd,CAAA,OAAA,EACA,YACA,EAAA,IAAA,EACA,IACmB,EAAA;AACnB,EAAO,OAAA,KAAA;AAAA,IACL,GAAG,OAAO,CAAA,sCAAA,EAAyC,YAAY,CAAS,MAAA,EAAA,IAAI,SAAS,IAAI,CAAA,CAAA;AAAA,GAC3F,CAAE,KAAK,CAAY,QAAA,KAAA;AACjB,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAM,MAAA,IAAI,KAAM,CAAA,QAAA,CAAS,UAAU,CAAA,CAAA;AAAA,KACrC;AACA,IAAA,OAAO,SAAS,IAAK,EAAA,CAAA;AAAA,GACtB,CAAA,CAAA;AACH,CAAA;AAEgB,SAAA,WAAA,CACd,SACA,YACkB,EAAA;AAClB,EAAO,OAAA,KAAA;AAAA,IACL,CAAA,EAAG,OAAO,CAAA,yCAAA,EAA4C,YAAY,CAAA,CAAA;AAAA,GACpE,CAAE,KAAK,CAAY,QAAA,KAAA;AACjB,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAM,MAAA,IAAI,KAAM,CAAA,QAAA,CAAS,UAAU,CAAA,CAAA;AAAA,KACrC;AACA,IAAA,OAAO,SAAS,IAAK,EAAA,CAAA;AAAA,GACtB,CAAA,CAAA;AACH,CAAA;AAEgB,SAAA,cAAA,CACd,OACA,EAAA,YAAA,EACA,UACgB,EAAA;AAChB,EAAO,OAAA,KAAA;AAAA,IACL,CAAG,EAAA,OAAO,CAAuB,oBAAA,EAAA,UAAU,4BAA4B,YAAY,CAAA,CAAA;AAAA,GACrF,CAAE,KAAK,CAAY,QAAA,KAAA;AACjB,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAM,MAAA,IAAI,KAAM,CAAA,QAAA,CAAS,UAAU,CAAA,CAAA;AAAA,KACrC;AACA,IAAA,OAAO,SAAS,IAAK,EAAA,CAAA;AAAA,GACtB,CAAA,CAAA;AACH;;ACxCO,SAAS,+BACd,MACoB,EAAA;AACpB,EAAA,MAAM,kBAAkB,MAAO,CAAA,iBAAA;AAAA,IAC7B,uCAAA;AAAA,GACF,CAAA;AACA,EAAA,IAAI,CAAC,eAAiB,EAAA;AACpB,IAAA,OAAO,EAAC,CAAA;AAAA,GACV;AACA,EAAO,OAAA,eAAA,CACJ,MACA,CAAA,GAAA;AAAA,IAAI,QACH,6BAA8B,CAAA,EAAA,EAAI,eAAgB,CAAA,SAAA,CAAU,EAAE,CAAC,CAAA;AAAA,GACjE,CAAA;AACJ,CAAA;AAEA,SAAS,6BAAA,CACP,IACA,MACkB,EAAA;AAClB,EAAM,MAAA,OAAA,GAAU,MAAO,CAAA,SAAA,CAAU,SAAS,CAAA,CAAA;AAC1C,EAAM,MAAA,WAAA,GAAc,MAAO,CAAA,SAAA,CAAU,aAAa,CAAA,CAAA;AAClD,EAAM,MAAA,WAAA,GAAc,MAAO,CAAA,iBAAA,CAAkB,aAAa,CAAA,CAAA;AAC1D,EAAM,MAAA,UAAA,GAAa,MAAO,CAAA,iBAAA,CAAkB,YAAY,CAAA,CAAA;AACxD,EAAA,MAAM,SAAY,GAAA,MAAA,CAAO,kBAAmB,CAAA,WAAW,CAAK,IAAA,IAAA,CAAA;AAE5D,EAAM,MAAA,QAAA,GAAW,MAAO,CAAA,GAAA,CAAI,UAAU,CAAA,GAClCA,kDAAqC,MAAO,CAAA,SAAA,CAAU,UAAU,CAAC,CACjE,GAAA,KAAA,CAAA,CAAA;AAEJ,EAAO,OAAA;AAAA,IACL,EAAA;AAAA,IACA,OAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA,SAAA;AAAA,IACA,QAAA;AAAA,GACF,CAAA;AACF;;ACfO,MAAM,2BAAsD,CAAA;AAAA,EACjE,OAAe,mBAA8B,GAAA,GAAA,CAAA;AAAA,EAC5B,GAAA,CAAA;AAAA,EACA,OAAA,CAAA;AAAA,EACA,WAAA,CAAA;AAAA,EACA,MAAA,CAAA;AAAA,EACA,UAAA,CAAA;AAAA,EACT,UAAA,CAAA;AAAA,EAER,OAAO,UACL,CAAA,UAAA,EACA,OAK+B,EAAA;AAC/B,IAAM,MAAA,eAAA,GAAkB,+BAA+B,UAAU,CAAA,CAAA;AAEjE,IAAA,IAAI,CAAC,OAAA,CAAQ,QAAY,IAAA,CAAC,QAAQ,SAAW,EAAA;AAC3C,MAAM,MAAA,IAAI,MAAM,gDAAgD,CAAA,CAAA;AAAA,KAClE;AAEA,IAAO,OAAA,eAAA,CAAgB,IAAI,CAAkB,cAAA,KAAA;AAC3C,MAAA,IAAI,CAAC,OAAA,CAAQ,QAAY,IAAA,CAAC,eAAe,QAAU,EAAA;AACjD,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,iFAAA,EAAoF,eAAe,EAAE,CAAA,CAAA,CAAA;AAAA,SACvG,CAAA;AAAA,OACF;AAEA,MAAI,IAAA,UAAA,CAAA;AAEJ,MAAI,IAAA,OAAA,CAAQ,SAAa,IAAA,cAAA,CAAe,QAAU,EAAA;AAEhD,QAAA,UAAA,GAAa,QAAQ,SAAU,CAAA,yBAAA;AAAA,UAC7B,cAAe,CAAA,QAAA;AAAA,SACjB,CAAA;AAAA,OACF,MAAA,IAAW,QAAQ,QAAU,EAAA;AAE3B,QAAA,UAAA,GAAa,OAAQ,CAAA,QAAA,CAAA;AAAA,OAChB,MAAA;AAEL,QAAM,MAAA,IAAI,MAAM,6CAA6C,CAAA,CAAA;AAAA,OAC/D;AAEA,MAAA,OAAO,IAAI,2BAAA;AAAA,QACT,cAAA;AAAA,QACA,OAAQ,CAAA,MAAA;AAAA,QACR,UAAA;AAAA,OACF,CAAA;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AAAA,EAEQ,WAAA,CACN,MACA,EAAA,MAAA,EACA,UACA,EAAA;AACA,IAAA,IAAA,CAAK,MAAM,MAAO,CAAA,EAAA,CAAA;AAClB,IAAA,IAAA,CAAK,UAAU,MAAO,CAAA,OAAA,CAAA;AACtB,IAAA,IAAA,CAAK,cAAc,MAAO,CAAA,WAAA,CAAA;AAC1B,IAAK,IAAA,CAAA,MAAA,GAAS,OAAO,KAAM,CAAA;AAAA,MACzB,MAAA,EAAQ,KAAK,eAAgB,EAAA;AAAA,KAC9B,CAAA,CAAA;AAED,IAAK,IAAA,CAAA,UAAA,GAAa,IAAK,CAAA,gBAAA,CAAiB,UAAU,CAAA,CAAA;AAAA,GACpD;AAAA,EAEQ,iBAAiB,UAA6C,EAAA;AACpE,IAAA,OAAO,YAAY;AACjB,MAAA,MAAM,MAAS,GAAA,CAAA,EAAG,IAAK,CAAA,eAAA,EAAiB,CAAA,IAAA,CAAA,CAAA;AACxC,MAAA,OAAO,WAAW,GAAI,CAAA;AAAA,QACpB,EAAI,EAAA,MAAA;AAAA,QACJ,IAAI,YAAY;AACd,UAAI,IAAA;AACF,YAAA,MAAM,KAAK,GAAI,EAAA,CAAA;AAAA,mBACR,KAAY,EAAA;AAEnB,YAAA,IAAA,CAAK,MAAO,CAAA,KAAA;AAAA,cACV,CAAA,oCAAA,EAAuC,KAAK,OAAO,CAAA,CAAA;AAAA,cACnD;AAAA;AAAA,gBAEE,MAAM,KAAM,CAAA,IAAA;AAAA,gBACZ,SAAS,KAAM,CAAA,OAAA;AAAA,gBACf,OAAO,KAAM,CAAA,KAAA;AAAA;AAAA,gBAEb,MAAA,EAAQ,MAAM,QAAU,EAAA,MAAA;AAAA,eAC1B;AAAA,aACF,CAAA;AAAA,WACF;AAAA,SACF;AAAA,OACD,CAAA,CAAA;AAAA,KACH,CAAA;AAAA,GACF;AAAA,EAEA,eAA0B,GAAA;AACxB,IAAO,OAAA,CAAA,4BAAA,EAA+B,KAAK,GAAG,CAAA,CAAA,CAAA;AAAA,GAChD;AAAA,EAEA,MAAM,QAAQ,UAAqD,EAAA;AACjE,IAAA,IAAA,CAAK,UAAa,GAAA,UAAA,CAAA;AAClB,IAAA,MAAM,KAAK,UAAW,EAAA,CAAA;AAAA,GACxB;AAAA,EAEA,MAAM,GAAqB,GAAA;AACzB,IAAI,IAAA,CAAC,KAAK,UAAY,EAAA;AACpB,MAAM,MAAA,IAAI,MAAM,iBAAiB,CAAA,CAAA;AAAA,KACnC;AAEA,IAAA,IAAA,CAAK,MAAO,CAAA,IAAA,CAAK,CAAuC,oCAAA,EAAA,IAAA,CAAK,OAAO,CAAE,CAAA,CAAA,CAAA;AAEtE,IAAA,MAAM,WAAqB,EAAC,CAAA;AAE5B,IAAA,IAAI,IAAe,GAAA,CAAA,CAAA;AACnB,IAAI,IAAA,QAAA,CAAA;AACJ,IAAI,IAAA,OAAA,CAAA;AACJ,IAAA,IAAI,aAAyB,GAAA,IAAA,CAAA;AAC7B,IAAA,OAAO,aAAe,EAAA;AACpB,MAAA,QAAA,GAAW,MAAM,YAAA;AAAA,QACf,IAAK,CAAA,OAAA;AAAA,QACL,IAAK,CAAA,WAAA;AAAA,QACL,IAAA;AAAA,QACA,2BAA4B,CAAA,mBAAA;AAAA,OAC9B,CAAA;AACA,MAAA,OAAA,GAAU,MAAM,WAAA,CAAY,IAAK,CAAA,OAAA,EAAS,KAAK,WAAW,CAAA,CAAA;AAC1D,MAAW,KAAA,MAAA,OAAA,IAAW,SAAS,QAAU,EAAA;AACvC,QAAA,MAAM,OAAU,GAAA,OAAA,CAAA;AAChB,QAAA,IAAA,CAAK,OAAO,KAAM,CAAA,CAAA,aAAA,EAAgB,OAAQ,CAAA,OAAA,CAAQ,IAAI,CAAE,CAAA,CAAA,CAAA;AAGxD,QAAA,MAAM,MAAS,GAAA,OAAA,CAAQ,QAAS,CAAA,IAAA,CAAK,CAAO,GAAA,KAAA;AAC1C,UAAI,IAAA,GAAA,CAAI,OAAQ,CAAA,UAAA,KAAe,KAAW,CAAA,EAAA;AACxC,YAAA,OAAO,GAAI,CAAA,OAAA,CAAQ,UAAe,KAAA,OAAA,CAAQ,OAAQ,CAAA,EAAA,CAAA;AAAA,WACpD;AACA,UAAO,OAAA,KAAA,CAAA;AAAA,SACR,CAAA,CAAA;AAED,QAAA,MAAM,QAAQ,MAAM,cAAA;AAAA,UAClB,IAAK,CAAA,OAAA;AAAA,UACL,IAAK,CAAA,WAAA;AAAA,UACL,QAAQ,OAAQ,CAAA,EAAA;AAAA,SAClB,CAAA;AACA,QAAA,IAAI,WAAW,KAAW,CAAA,EAAA;AACxB,UAAA,IAAA,CAAK,MAAO,CAAA,IAAA,CAAK,IAAK,CAAA,SAAA,CAAU,MAAM,CAAC,CAAA,CAAA;AACvC,UAAA,MAAM,YAAuB,IAAK,CAAA,yBAAA;AAAA,YAChC,OAAA;AAAA,YACA,MAAA;AAAA,YACA,KAAA;AAAA,WACF,CAAA;AACA,UAAA,QAAA,CAAS,KAAK,SAAS,CAAA,CAAA;AACvB,UAAA,IAAA,CAAK,OAAO,KAAM,CAAA,CAAA,qBAAA,EAAwB,OAAQ,CAAA,OAAA,CAAQ,IAAI,CAAE,CAAA,CAAA,CAAA;AAAA,SAClE;AAAA,OACF;AAEA,MAAA,IACE,QAAS,CAAA,QAAA,CAAS,MAClB,GAAA,2BAAA,CAA4B,mBAC5B,EAAA;AACA,QAAgB,aAAA,GAAA,KAAA,CAAA;AAAA,OAClB;AACA,MAAA,IAAA,EAAA,CAAA;AAAA,KACF;AAEA,IAAA,IAAA,CAAK,MAAO,CAAA,IAAA,CAAK,CAA8B,2BAAA,EAAA,QAAA,CAAS,MAAM,CAAW,SAAA,CAAA,CAAA,CAAA;AAEzE,IAAM,MAAA,IAAA,CAAK,WAAW,aAAc,CAAA;AAAA,MAClC,IAAM,EAAA,MAAA;AAAA,MACN,QAAA,EAAU,QAAS,CAAA,GAAA,CAAI,CAAW,MAAA,MAAA;AAAA,QAChC,MAAA;AAAA,QACA,WAAA,EAAa,KAAK,eAAgB,EAAA;AAAA,OAClC,CAAA,CAAA;AAAA,KACH,CAAA,CAAA;AAAA,GACH;AAAA,EAEQ,yBAAA,CACN,OACA,EAAA,MAAA,EACA,KACW,EAAA;AACX,IAAA,MAAM,WAAW,CAAO,IAAA,EAAA,IAAA,CAAK,OAAO,CAAuB,oBAAA,EAAA,OAAA,CAAQ,QAAQ,EAAE,CAAA,CAAA,CAAA;AAE7E,IAAA,MAAM,IAAO,GAAA,IAAA,CAAK,KAAM,CAAA,MAAA,CAAO,QAAQ,IAAI,CAAA,CAAA;AAE3C,IAAO,OAAA;AAAA,MACL,IAAM,EAAA,KAAA;AAAA,MACN,UAAY,EAAA,uBAAA;AAAA,MACZ,QAAU,EAAA;AAAA,QACR,WAAa,EAAA;AAAA,UACX,CAACC,gCAAmB,GAAG,QAAA;AAAA,UACvB,CAACC,uCAA0B,GAAG,QAAA;AAAA,SAChC;AAAA;AAAA,QAEA,IAAM,EAAA,CAAA,EAAG,OAAQ,CAAA,OAAA,CAAQ,WAAW,CAAA,CAAA;AAAA,QACpC,aACE,IAAK,CAAA,IAAA,CAAK,eAAe,CAAY,SAAA,EAAA,OAAA,CAAQ,QAAQ,WAAW,CAAA,CAAA;AAAA;AAAA;AAAA,QAGlE,KAAO,EAAA;AAAA,UACL;AAAA,YACE,KAAK,CAAG,EAAA,IAAA,CAAK,OAAO,CAAuB,oBAAA,EAAA,OAAA,CAAQ,QAAQ,EAAE,CAAA,CAAA;AAAA,YAC7D,KAAO,EAAA,iBAAA;AAAA,WACT;AAAA,UACA;AAAA,YACE,GAAK,EAAA,CAAA,EAAG,KAAM,CAAA,KAAA,CAAM,gBAAgB,CAAA,CAAA;AAAA,YACpC,KAAO,EAAA,0BAAA;AAAA,WACT;AAAA,UACA;AAAA,YACE,GAAK,EAAA,CAAA,EAAG,KAAM,CAAA,KAAA,CAAM,QAAQ,CAAA,CAAA;AAAA,YAC5B,KAAO,EAAA,6BAAA;AAAA,WACT;AAAA,SACF;AAAA,OACF;AAAA,MACA,IAAM,EAAA;AAAA,QACJ,IAAM,EAAA,SAAA;AAAA,QACN,WAAW,IAAK,CAAA,GAAA;AAAA,QAChB,MAAQ,EAAA,QAAA;AAAA,QACR,KAAO,EAAA,QAAA;AAAA,QACP,UAAA,EAAY,OAAO,OAAQ,CAAA,IAAA;AAAA,OAC7B;AAAA,KACF,CAAA;AAAA,GACF;AACF;;;;;;;"}
|