@backstage-community/plugin-3scale-backend 1.8.4 → 2.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 +279 -23
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +10 -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 +10 -17
- 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
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 577034b: **BREAKING** migrate to the new backend and remove deprecations
|
|
8
|
+
|
|
9
|
+
## 1.8.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- c6e87b8: Backstage version bump to v1.31.1
|
|
14
|
+
|
|
3
15
|
## 1.8.4
|
|
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,284 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require('@backstage/catalog-model');
|
|
5
|
-
require('@backstage/backend-tasks');
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
ThreeScaleApiEntityProvider.ThreeScaleApiEntityProvider.fromConfig(env.config, {
|
|
12
|
-
logger: env.logger,
|
|
13
|
-
scheduler: env.scheduler,
|
|
14
|
-
schedule: env.scheduler.createScheduledTaskRunner({
|
|
15
|
-
frequency: { minutes: 1 },
|
|
16
|
-
timeout: { minutes: 1 }
|
|
17
|
-
})
|
|
18
|
-
})
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
};
|
|
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');
|
|
22
9
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
10
|
+
function listServices(baseUrl, access_token, page, size) {
|
|
11
|
+
return fetch(
|
|
12
|
+
`${baseUrl}/admin/api/services.json?access_token=${access_token}&page=${page}&size=${size}`
|
|
13
|
+
).then((response) => {
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
throw new Error(response.statusText);
|
|
16
|
+
}
|
|
17
|
+
return response.json();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function listApiDocs(baseUrl, access_token) {
|
|
21
|
+
return fetch(
|
|
22
|
+
`${baseUrl}/admin/api/active_docs.json?access_token=${access_token}`
|
|
23
|
+
).then((response) => {
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(response.statusText);
|
|
26
|
+
}
|
|
27
|
+
return response.json();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function getProxyConfig(baseUrl, access_token, service_id) {
|
|
31
|
+
return fetch(
|
|
32
|
+
`${baseUrl}/admin/api/services/${service_id}/proxy.json?access_token=${access_token}`
|
|
33
|
+
).then((response) => {
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(response.statusText);
|
|
36
|
+
}
|
|
37
|
+
return response.json();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readThreeScaleApiEntityConfigs(config) {
|
|
42
|
+
const providerConfigs = config.getOptionalConfig(
|
|
43
|
+
"catalog.providers.threeScaleApiEntity"
|
|
44
|
+
);
|
|
45
|
+
if (!providerConfigs) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return providerConfigs.keys().map(
|
|
49
|
+
(id) => readThreeScaleApiEntityConfig(id, providerConfigs.getConfig(id))
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
function readThreeScaleApiEntityConfig(id, config) {
|
|
53
|
+
const baseUrl = config.getString("baseUrl");
|
|
54
|
+
const accessToken = config.getString("accessToken");
|
|
55
|
+
const systemLabel = config.getOptionalString("systemLabel");
|
|
56
|
+
const ownerLabel = config.getOptionalString("ownerLabel");
|
|
57
|
+
const addLabels = config.getOptionalBoolean("addLabels") || true;
|
|
58
|
+
const schedule = config.has("schedule") ? backendPluginApi.readSchedulerServiceTaskScheduleDefinitionFromConfig(
|
|
59
|
+
config.getConfig("schedule")
|
|
60
|
+
) : void 0;
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
baseUrl,
|
|
64
|
+
accessToken,
|
|
65
|
+
systemLabel,
|
|
66
|
+
ownerLabel,
|
|
67
|
+
addLabels,
|
|
68
|
+
schedule
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class ThreeScaleApiEntityProvider {
|
|
73
|
+
static SERVICES_FETCH_SIZE = 500;
|
|
74
|
+
env;
|
|
75
|
+
baseUrl;
|
|
76
|
+
accessToken;
|
|
77
|
+
logger;
|
|
78
|
+
scheduleFn;
|
|
79
|
+
connection;
|
|
80
|
+
static fromConfig(deps, options) {
|
|
81
|
+
const providerConfigs = readThreeScaleApiEntityConfigs(deps.config);
|
|
82
|
+
if (!options.schedule && !options.scheduler) {
|
|
83
|
+
throw new Error("Either schedule or scheduler must be provided.");
|
|
84
|
+
}
|
|
85
|
+
return providerConfigs.map((providerConfig) => {
|
|
86
|
+
if (!options.schedule && !providerConfig.schedule) {
|
|
87
|
+
throw new errors.InputError(
|
|
88
|
+
`No schedule provided via config for ThreeScaleApiEntityProvider:${providerConfig.id}.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
let taskRunner;
|
|
92
|
+
if (options.scheduler && providerConfig.schedule) {
|
|
93
|
+
taskRunner = options.scheduler.createScheduledTaskRunner(
|
|
94
|
+
providerConfig.schedule
|
|
95
|
+
);
|
|
96
|
+
} else if (options.schedule) {
|
|
97
|
+
taskRunner = options.schedule;
|
|
98
|
+
} else {
|
|
99
|
+
throw new Error("Neither schedule nor scheduler is provided.");
|
|
100
|
+
}
|
|
101
|
+
return new ThreeScaleApiEntityProvider(
|
|
102
|
+
providerConfig,
|
|
103
|
+
deps.logger,
|
|
104
|
+
taskRunner
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
constructor(config, logger, taskRunner) {
|
|
109
|
+
this.env = config.id;
|
|
110
|
+
this.baseUrl = config.baseUrl;
|
|
111
|
+
this.accessToken = config.accessToken;
|
|
112
|
+
this.logger = logger.child({
|
|
113
|
+
target: this.getProviderName()
|
|
114
|
+
});
|
|
115
|
+
this.scheduleFn = this.createScheduleFn(taskRunner);
|
|
116
|
+
}
|
|
117
|
+
createScheduleFn(taskRunner) {
|
|
118
|
+
return async () => {
|
|
119
|
+
const taskId = `${this.getProviderName()}:run`;
|
|
120
|
+
return taskRunner.run({
|
|
121
|
+
id: taskId,
|
|
122
|
+
fn: async () => {
|
|
123
|
+
try {
|
|
124
|
+
await this.run();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (errors.isError(error)) {
|
|
127
|
+
this.logger.error(
|
|
128
|
+
`Error while syncing 3scale API from ${this.baseUrl}`,
|
|
129
|
+
{
|
|
130
|
+
// Default Error properties:
|
|
131
|
+
name: error.name,
|
|
132
|
+
message: error.message,
|
|
133
|
+
stack: error.stack,
|
|
134
|
+
// Additional status code if available:
|
|
135
|
+
status: error.response?.status
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
getProviderName() {
|
|
145
|
+
return `ThreeScaleApiEntityProvider:${this.env}`;
|
|
146
|
+
}
|
|
147
|
+
async connect(connection) {
|
|
148
|
+
this.connection = connection;
|
|
149
|
+
await this.scheduleFn();
|
|
150
|
+
}
|
|
151
|
+
async run() {
|
|
152
|
+
if (!this.connection) {
|
|
153
|
+
throw new errors.NotFoundError("Not initialized");
|
|
154
|
+
}
|
|
155
|
+
this.logger.info(`Discovering ApiEntities from 3scale ${this.baseUrl}`);
|
|
156
|
+
const entities = [];
|
|
157
|
+
let page = 0;
|
|
158
|
+
let services;
|
|
159
|
+
let apiDocs;
|
|
160
|
+
let fetchServices = true;
|
|
161
|
+
while (fetchServices) {
|
|
162
|
+
services = await listServices(
|
|
163
|
+
this.baseUrl,
|
|
164
|
+
this.accessToken,
|
|
165
|
+
page,
|
|
166
|
+
ThreeScaleApiEntityProvider.SERVICES_FETCH_SIZE
|
|
167
|
+
);
|
|
168
|
+
apiDocs = await listApiDocs(this.baseUrl, this.accessToken);
|
|
169
|
+
for (const element of services.services) {
|
|
170
|
+
const service = element;
|
|
171
|
+
this.logger.debug(`Find service ${service.service.name}`);
|
|
172
|
+
const apiDoc = apiDocs.api_docs.find((obj) => {
|
|
173
|
+
if (obj.api_doc.service_id !== void 0) {
|
|
174
|
+
return obj.api_doc.service_id === service.service.id;
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
});
|
|
178
|
+
const proxy = await getProxyConfig(
|
|
179
|
+
this.baseUrl,
|
|
180
|
+
this.accessToken,
|
|
181
|
+
service.service.id
|
|
182
|
+
);
|
|
183
|
+
if (apiDoc !== void 0) {
|
|
184
|
+
this.logger.info(JSON.stringify(apiDoc));
|
|
185
|
+
const apiEntity = this.buildApiEntityFromService(
|
|
186
|
+
service,
|
|
187
|
+
apiDoc,
|
|
188
|
+
proxy
|
|
189
|
+
);
|
|
190
|
+
entities.push(apiEntity);
|
|
191
|
+
this.logger.debug(`Discovered ApiEntity ${service.service.name}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (services.services.length < ThreeScaleApiEntityProvider.SERVICES_FETCH_SIZE) {
|
|
195
|
+
fetchServices = false;
|
|
196
|
+
}
|
|
197
|
+
page++;
|
|
198
|
+
}
|
|
199
|
+
this.logger.info(`Applying the mutation with ${entities.length} entities`);
|
|
200
|
+
await this.connection.applyMutation({
|
|
201
|
+
type: "full",
|
|
202
|
+
entities: entities.map((entity) => ({
|
|
203
|
+
entity,
|
|
204
|
+
locationKey: this.getProviderName()
|
|
205
|
+
}))
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
buildApiEntityFromService(service, apiDoc, proxy) {
|
|
209
|
+
const location = `url:${this.baseUrl}/apiconfig/services/${service.service.id}`;
|
|
210
|
+
const spec = JSON.parse(apiDoc.api_doc.body);
|
|
211
|
+
return {
|
|
212
|
+
kind: "API",
|
|
213
|
+
apiVersion: "backstage.io/v1alpha1",
|
|
214
|
+
metadata: {
|
|
215
|
+
annotations: {
|
|
216
|
+
[catalogModel.ANNOTATION_LOCATION]: location,
|
|
217
|
+
[catalogModel.ANNOTATION_ORIGIN_LOCATION]: location
|
|
218
|
+
},
|
|
219
|
+
// TODO: add tenant name
|
|
220
|
+
name: `${service.service.system_name}`,
|
|
221
|
+
description: spec.info.description || `Version: ${service.service.description}`,
|
|
222
|
+
// TODO: add labels
|
|
223
|
+
// labels: this.getApiEntityLabels(service),
|
|
224
|
+
links: [
|
|
225
|
+
{
|
|
226
|
+
url: `${this.baseUrl}/apiconfig/services/${service.service.id}`,
|
|
227
|
+
title: "3scale Overview"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
url: `${proxy.proxy.sandbox_endpoint}`,
|
|
231
|
+
title: "Staging Apicast Endpoint"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
url: `${proxy.proxy.endpoint}`,
|
|
235
|
+
title: "Production Apicast Endpoint"
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
},
|
|
239
|
+
spec: {
|
|
240
|
+
type: "openapi",
|
|
241
|
+
lifecycle: this.env,
|
|
242
|
+
system: "3scale",
|
|
243
|
+
owner: "3scale",
|
|
244
|
+
definition: apiDoc.api_doc.body
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const catalogModule3ScaleEntityProvider = backendPluginApi.createBackendModule({
|
|
251
|
+
moduleId: "catalog-backend-module-3scale",
|
|
252
|
+
pluginId: "catalog",
|
|
253
|
+
register(env) {
|
|
254
|
+
env.registerInit({
|
|
255
|
+
deps: {
|
|
256
|
+
catalog: alpha.catalogProcessingExtensionPoint,
|
|
257
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
258
|
+
logger: backendPluginApi.coreServices.logger,
|
|
259
|
+
scheduler: backendPluginApi.coreServices.scheduler
|
|
260
|
+
},
|
|
261
|
+
async init({ catalog, config, logger, scheduler }) {
|
|
262
|
+
catalog.addEntityProvider(
|
|
263
|
+
ThreeScaleApiEntityProvider.fromConfig(
|
|
264
|
+
{ config, logger },
|
|
265
|
+
{
|
|
266
|
+
scheduler,
|
|
267
|
+
schedule: scheduler.createScheduledTaskRunner({
|
|
268
|
+
frequency: { minutes: 30 },
|
|
269
|
+
timeout: { minutes: 3 }
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
exports.ThreeScaleApiEntityProvider = ThreeScaleApiEntityProvider;
|
|
280
|
+
exports.default = catalogModule3ScaleEntityProvider;
|
|
281
|
+
exports.getProxyConfig = getProxyConfig;
|
|
282
|
+
exports.listApiDocs = listApiDocs;
|
|
283
|
+
exports.listServices = listServices;
|
|
28
284
|
//# 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/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 {\n SchedulerServiceTaskRunner,\n SchedulerService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\n\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 type { 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 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 }\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 // 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","/*\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","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;
|
|
@@ -114,10 +115,12 @@ declare class ThreeScaleApiEntityProvider implements EntityProvider {
|
|
|
114
115
|
private readonly logger;
|
|
115
116
|
private readonly scheduleFn;
|
|
116
117
|
private connection?;
|
|
117
|
-
static fromConfig(
|
|
118
|
+
static fromConfig(deps: {
|
|
119
|
+
config: Config;
|
|
118
120
|
logger: LoggerService;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
}, options: {
|
|
122
|
+
schedule: SchedulerServiceTaskRunner;
|
|
123
|
+
scheduler: SchedulerService;
|
|
121
124
|
}): ThreeScaleApiEntityProvider[];
|
|
122
125
|
private constructor();
|
|
123
126
|
private createScheduleFn;
|
|
@@ -127,6 +130,4 @@ declare class ThreeScaleApiEntityProvider implements EntityProvider {
|
|
|
127
130
|
private buildApiEntityFromService;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
export { ThreeScaleApiEntityProvider, dynamicPluginInstaller, getProxyConfig, listApiDocs, listServices };
|
|
133
|
+
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": "2.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": {
|
|
@@ -39,16 +34,16 @@
|
|
|
39
34
|
"tsc": "tsc"
|
|
40
35
|
},
|
|
41
36
|
"dependencies": {
|
|
42
|
-
"@backstage/backend-
|
|
43
|
-
"@backstage/
|
|
44
|
-
"@backstage/
|
|
45
|
-
"@backstage/
|
|
46
|
-
"@backstage/catalog-model": "^1.6.0",
|
|
47
|
-
"@backstage/config": "^1.2.0",
|
|
48
|
-
"@backstage/plugin-catalog-node": "^1.12.5"
|
|
37
|
+
"@backstage/backend-plugin-api": "^1.0.0",
|
|
38
|
+
"@backstage/catalog-model": "^1.7.0",
|
|
39
|
+
"@backstage/errors": "^1.2.4",
|
|
40
|
+
"@backstage/plugin-catalog-node": "^1.13.0"
|
|
49
41
|
},
|
|
50
42
|
"devDependencies": {
|
|
51
|
-
"@backstage/
|
|
43
|
+
"@backstage/backend-defaults": "^0.5.0",
|
|
44
|
+
"@backstage/cli": "^0.27.1",
|
|
45
|
+
"@backstage/config": "^1.2.0",
|
|
46
|
+
"@backstage/plugin-catalog-backend": "^1.26.0",
|
|
52
47
|
"@janus-idp/cli": "1.13.1",
|
|
53
48
|
"@types/supertest": "6.0.2",
|
|
54
49
|
"msw": "1.3.3",
|
|
@@ -59,9 +54,7 @@
|
|
|
59
54
|
"config.d.ts",
|
|
60
55
|
"dist-dynamic/*.*",
|
|
61
56
|
"dist-dynamic/dist/**",
|
|
62
|
-
"
|
|
63
|
-
"app-config.yaml",
|
|
64
|
-
"alpha"
|
|
57
|
+
"app-config.yaml"
|
|
65
58
|
],
|
|
66
59
|
"configSchema": "config.d.ts",
|
|
67
60
|
"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":"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"}
|