@backstage-community/plugin-catalog-backend-module-apiiro-entity-processor 0.1.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 +85 -0
- package/dist/index.cjs.js +10 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/module.cjs.js +24 -0
- package/dist/module.cjs.js.map +1 -0
- package/dist/processor/ApiiroAnnotationProcessor.cjs.js +297 -0
- package/dist/processor/ApiiroAnnotationProcessor.cjs.js.map +1 -0
- package/package.json +58 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @backstage-community/plugin-catalog-backend-module-apiiro-entity-processor
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8407557: Added new [Apiiro](https://apiiro.com/) plugin. Check out the plugins [README.md](https://github.com/backstage/community-plugins/tree/main/workspaces/apiiro/plugins/apiiro) for more details!
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [8407557]
|
|
12
|
+
- @backstage-community/plugin-apiiro-common@0.1.0
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @backstage-community/plugin-catalog-backend-module-apiiro-entity-processor
|
|
2
|
+
|
|
3
|
+
Catalog backend module that automatically adds Apiiro annotations to Backstage entities based on their `backstage.io/source-location`. It is used together with the Apiiro frontend and backend plugins to simplify onboarding and keep Apiiro-related annotations consistent.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
When enabled, this module:
|
|
8
|
+
|
|
9
|
+
- Derives the Apiiro repository identifier from the entity's source location.
|
|
10
|
+
- Adds the following annotations when they are missing:
|
|
11
|
+
- `apiiro.com/repo-id`
|
|
12
|
+
- `apiiro.com/allow-metrics-view`
|
|
13
|
+
- **Does not overwrite** existing Apiiro annotations if they are already set on the entity.
|
|
14
|
+
|
|
15
|
+
This helps you avoid manually managing Apiiro annotations on every entity in your catalog.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
From your Backstage root directory, install the module in the backend:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
yarn --cwd packages/backend add @backstage-community/plugin-catalog-backend-module-apiiro-entity-processor
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### 1. Register the entity processor
|
|
28
|
+
|
|
29
|
+
In `packages/backend/src/index.ts`, register the module with the backend:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
backend.add(
|
|
33
|
+
import(
|
|
34
|
+
'@backstage-community/plugin-catalog-backend-module-apiiro-entity-processor'
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
After the catalog processing interval elapses, entities that have a valid `backstage.io/source-location`
|
|
40
|
+
will be updated with Apiiro annotations when they are missing.
|
|
41
|
+
|
|
42
|
+
### 2. Apiiro annotations
|
|
43
|
+
|
|
44
|
+
The processor works with the following annotations:
|
|
45
|
+
|
|
46
|
+
- `apiiro.com/repo-id`: `<repo-key>`
|
|
47
|
+
- `apiiro.com/allow-metrics-view`: `"true"` or `"false"` (controls whether the Metrics view appears in the Apiiro Tab and Apiiro Widget)
|
|
48
|
+
|
|
49
|
+
### Notes
|
|
50
|
+
|
|
51
|
+
- Annotation values are derived from the value of `backstage.io/source-location`. If `backstage.io/source-location` is not present, Apiiro annotations will not be added.
|
|
52
|
+
- If Apiiro annotations already exist on an entity, they take precedence and will **not** be overwritten.
|
|
53
|
+
|
|
54
|
+
## Permissions and Metrics View
|
|
55
|
+
|
|
56
|
+
Together with the Apiiro backend plugin configuration, you can further restrict
|
|
57
|
+
which entities are allowed to show Apiiro metrics (tiles / dashboards) and
|
|
58
|
+
widgets. In `app-config.yaml` or `app-config.production.yaml`:
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
apiiro:
|
|
62
|
+
accessToken: ${APIIRO_TOKEN}
|
|
63
|
+
defaultAllowMetricsView: true
|
|
64
|
+
# Optional configuration to allow or disallow metric views for specific entities
|
|
65
|
+
annotationControl:
|
|
66
|
+
entityNames:
|
|
67
|
+
- component:<namespace>/<entity-name>
|
|
68
|
+
exclude: true
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Where the parameters in the `annotationControl`:
|
|
72
|
+
|
|
73
|
+
- `entityNames` is a list of entity references to control the metrics view access.
|
|
74
|
+
- `exclude: true` → **blocklist mode** (allow all entities except those listed).
|
|
75
|
+
- `exclude: false` → **allowlist mode** (deny all entities except those listed).
|
|
76
|
+
|
|
77
|
+
The `apiiro.com/allow-metrics-view` annotation and the above configuration
|
|
78
|
+
together determine whether a given entity can display metrics on Apiiro Tab and Apiiro Widget.
|
|
79
|
+
If you configure this list it will override the `defaultAllowMetricsView` configuration.
|
|
80
|
+
|
|
81
|
+
## Development
|
|
82
|
+
|
|
83
|
+
This module is developed as part of the Apiiro Backstage integration.
|
|
84
|
+
See the root repository `README.md` and the Apiiro backend plugin README for
|
|
85
|
+
more details on local development and configuration.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var alpha = require('@backstage/plugin-catalog-node/alpha');
|
|
5
|
+
var ApiiroAnnotationProcessor = require('./processor/ApiiroAnnotationProcessor.cjs.js');
|
|
6
|
+
|
|
7
|
+
const catalogModuleApiiroEntityProcessor = backendPluginApi.createBackendModule({
|
|
8
|
+
pluginId: "catalog",
|
|
9
|
+
moduleId: "apiiro-entity-processor",
|
|
10
|
+
register(reg) {
|
|
11
|
+
reg.registerInit({
|
|
12
|
+
deps: {
|
|
13
|
+
catalog: alpha.catalogProcessingExtensionPoint,
|
|
14
|
+
config: backendPluginApi.coreServices.rootConfig
|
|
15
|
+
},
|
|
16
|
+
async init({ catalog, config }) {
|
|
17
|
+
catalog.addProcessor(new ApiiroAnnotationProcessor.ApiiroAnnotationProcessor(config));
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
exports.catalogModuleApiiroEntityProcessor = catalogModuleApiiroEntityProcessor;
|
|
24
|
+
//# sourceMappingURL=module.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright 2025 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';\nimport { ApiiroAnnotationProcessor } from './processor';\n\n/**\n * @public\n */\nexport const catalogModuleApiiroEntityProcessor = createBackendModule({\n pluginId: 'catalog',\n moduleId: 'apiiro-entity-processor',\n register(reg) {\n reg.registerInit({\n deps: {\n catalog: catalogProcessingExtensionPoint,\n config: coreServices.rootConfig,\n },\n async init({ catalog, config }) {\n catalog.addProcessor(new ApiiroAnnotationProcessor(config));\n },\n });\n },\n});\n"],"names":["createBackendModule","catalogProcessingExtensionPoint","coreServices","ApiiroAnnotationProcessor"],"mappings":";;;;;;AA0BO,MAAM,qCAAqCA,oCAAA,CAAoB;AAAA,EACpE,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,yBAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,OAAA,EAASC,qCAAA;AAAA,QACT,QAAQC,6BAAA,CAAa;AAAA,OACvB;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,OAAA,EAAS,QAAO,EAAG;AAC9B,QAAA,OAAA,CAAQ,YAAA,CAAa,IAAIC,mDAAA,CAA0B,MAAM,CAAC,CAAA;AAAA,MAC5D;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fetch = require('node-fetch');
|
|
4
|
+
var pluginApiiroCommon = require('@backstage-community/plugin-apiiro-common');
|
|
5
|
+
|
|
6
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
|
|
9
|
+
|
|
10
|
+
const BACKSTAGE_SOURCE_LOCATION_ANNOTATION = "backstage.io/source-location";
|
|
11
|
+
class ApiiroAnnotationProcessor {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
static CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
16
|
+
// 1 hour
|
|
17
|
+
static PAGE_LIMIT = 1e3;
|
|
18
|
+
static MAX_PAGES = 1e3;
|
|
19
|
+
static DEFAULT_NAMESPACE = "default";
|
|
20
|
+
repoCache = {
|
|
21
|
+
data: /* @__PURE__ */ new Map(),
|
|
22
|
+
lastFetched: 0,
|
|
23
|
+
isRefreshing: false
|
|
24
|
+
};
|
|
25
|
+
getProcessorName() {
|
|
26
|
+
return "ApiiroAnnotationProcessor";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Determines if an entity should be processed by this processor.
|
|
30
|
+
* Only Component entities are processed.
|
|
31
|
+
*/
|
|
32
|
+
shouldProcessEntity(entity) {
|
|
33
|
+
return entity.kind === "Component";
|
|
34
|
+
}
|
|
35
|
+
extractRepoUrlFromSourceLocation(sourceLocation) {
|
|
36
|
+
try {
|
|
37
|
+
if (!sourceLocation) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const matches = sourceLocation.match(
|
|
41
|
+
/https?:\/\/[a-zA-Z0-9\-.]+\.[a-zA-Z]{2,}(:[0-9]{1,5})?(\/.*)?/g
|
|
42
|
+
);
|
|
43
|
+
if (!matches) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const url = new URL(matches[0]);
|
|
47
|
+
const hostname = url.host.toLowerCase();
|
|
48
|
+
let repoPath = null;
|
|
49
|
+
if (hostname === "dev.azure.com") {
|
|
50
|
+
const pathMatch = url.pathname.match(
|
|
51
|
+
/^\/([^/]+)\/([^/]+)\/_git\/([^/]+)(?:\/.*)?$/
|
|
52
|
+
);
|
|
53
|
+
if (pathMatch) {
|
|
54
|
+
repoPath = `/${pathMatch[1]}/${pathMatch[2]}/_git/${pathMatch[3]}`;
|
|
55
|
+
}
|
|
56
|
+
} else if (hostname.includes("gitlab")) {
|
|
57
|
+
const cleanPath = url.pathname.replace(/\/-\/.*$/, "");
|
|
58
|
+
const pathMatch = cleanPath.match(/^(\/[^/]+\/[^/]+(?:\/[^/]+)*)$/);
|
|
59
|
+
if (pathMatch) {
|
|
60
|
+
repoPath = pathMatch[1];
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
const pathMatch = url.pathname.match(/^\/([^/]+)\/([^/]+)(?:\/.*)?$/);
|
|
64
|
+
if (pathMatch) {
|
|
65
|
+
repoPath = `/${pathMatch[1]}/${pathMatch[2]}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!repoPath) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return `${url.protocol}//${hostname}${repoPath}`;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Retrieves the Apiiro access token from configuration.
|
|
78
|
+
* @throws {Error} If the access token is not configured
|
|
79
|
+
*/
|
|
80
|
+
getAccessToken() {
|
|
81
|
+
const accessToken = this.config.getOptionalString("apiiro.accessToken");
|
|
82
|
+
return accessToken;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Fetches a single page of repositories from the Apiiro API.
|
|
86
|
+
* @param pageCursor - Optional cursor for pagination
|
|
87
|
+
* @returns Page of repositories with next cursor
|
|
88
|
+
*/
|
|
89
|
+
async fetchRepositoriesPage(accessToken, pageCursor) {
|
|
90
|
+
const baseUrl = pluginApiiroCommon.APIIRO_DEFAULT_BASE_URL;
|
|
91
|
+
const params = new URLSearchParams();
|
|
92
|
+
params.append("limit", ApiiroAnnotationProcessor.PAGE_LIMIT.toString());
|
|
93
|
+
if (pageCursor) {
|
|
94
|
+
params.append("next", pageCursor);
|
|
95
|
+
}
|
|
96
|
+
const url = `${baseUrl}/rest-api/v2/repositories?${params.toString()}`;
|
|
97
|
+
const response = await fetch__default.default(url, {
|
|
98
|
+
method: "GET",
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${accessToken}`,
|
|
101
|
+
"Content-Type": "application/json"
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const errorMessage = `Failed to fetch repositories from Apiiro API. Status: ${response.status} ${response.statusText}`;
|
|
106
|
+
throw new Error(errorMessage);
|
|
107
|
+
}
|
|
108
|
+
const data = await response.json();
|
|
109
|
+
return {
|
|
110
|
+
items: data.items || [],
|
|
111
|
+
next: data.next || null
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Fetches all repositories from the Apiiro API with pagination.
|
|
116
|
+
* @returns All repositories with total count
|
|
117
|
+
* @throws {Error} If pagination limit is exceeded
|
|
118
|
+
*/
|
|
119
|
+
async fetchAllRepositories() {
|
|
120
|
+
const items = [];
|
|
121
|
+
let nextCursor = void 0;
|
|
122
|
+
let pageCount = 0;
|
|
123
|
+
do {
|
|
124
|
+
pageCount++;
|
|
125
|
+
if (pageCount > ApiiroAnnotationProcessor.MAX_PAGES) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Pagination limit exceeded: Maximum of ${ApiiroAnnotationProcessor.MAX_PAGES} pages allowed. This may indicate an infinite loop or an unexpectedly large dataset. Fetched ${items.length} repositories so far.`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const accessToken = this.getAccessToken();
|
|
131
|
+
if (!accessToken) {
|
|
132
|
+
console.warn(
|
|
133
|
+
"[ApiiroAnnotationProcessor] Apiiro access token not configured. Please set apiiro.accessToken in your app-config."
|
|
134
|
+
);
|
|
135
|
+
return {
|
|
136
|
+
items: [],
|
|
137
|
+
totalCount: 0
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const page = await this.fetchRepositoriesPage(
|
|
141
|
+
accessToken,
|
|
142
|
+
nextCursor ?? void 0
|
|
143
|
+
);
|
|
144
|
+
items.push(...page.items);
|
|
145
|
+
nextCursor = page.next;
|
|
146
|
+
} while (nextCursor);
|
|
147
|
+
return { items, totalCount: items.length };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Optimized method that combines default branch selection and map building in a single loop.
|
|
151
|
+
* Groups repositories by URL, selects the best branch for each, and builds the URL->key mapping.
|
|
152
|
+
* @param repositories Array of all repository items
|
|
153
|
+
* @returns Map of repository URLs to repository keys
|
|
154
|
+
*/
|
|
155
|
+
buildRepositoryMapWithDefaultBranches(repositories) {
|
|
156
|
+
const repoMap = /* @__PURE__ */ new Map();
|
|
157
|
+
for (const repo of repositories) {
|
|
158
|
+
if (!repo.url) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (repoMap.has(repo.url)) {
|
|
162
|
+
if (repo.isDefaultBranch) {
|
|
163
|
+
repoMap.set(repo.url, {
|
|
164
|
+
key: repo.key,
|
|
165
|
+
isDefaultBranch: repo.isDefaultBranch
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
} else {
|
|
170
|
+
repoMap.set(repo.url, {
|
|
171
|
+
key: repo.key,
|
|
172
|
+
isDefaultBranch: repo.isDefaultBranch
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return repoMap;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Fetches all repositories and builds a name->key mapping.
|
|
180
|
+
* Automatically filters to default branches only.
|
|
181
|
+
* @returns Map of repository names to repository keys
|
|
182
|
+
*/
|
|
183
|
+
async fetchAllRepos() {
|
|
184
|
+
try {
|
|
185
|
+
const { items } = await this.fetchAllRepositories();
|
|
186
|
+
return this.buildRepositoryMapWithDefaultBranches(items);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
189
|
+
console.error(
|
|
190
|
+
`[ApiiroAnnotationProcessor] Error fetching repositories from Apiiro API: ${errorMessage}`
|
|
191
|
+
);
|
|
192
|
+
return /* @__PURE__ */ new Map();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Checks if the cache is expired.
|
|
197
|
+
*/
|
|
198
|
+
isCacheExpired() {
|
|
199
|
+
const now = Date.now();
|
|
200
|
+
return now - this.repoCache.lastFetched > ApiiroAnnotationProcessor.CACHE_TTL_MS || this.repoCache.data.size === 0;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Refreshes the repository cache from the Apiiro API.
|
|
204
|
+
* Prevents concurrent refreshes using a flag.
|
|
205
|
+
*/
|
|
206
|
+
async refreshCacheIfNeeded() {
|
|
207
|
+
if (!this.isCacheExpired() || this.repoCache.isRefreshing) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
this.repoCache.isRefreshing = true;
|
|
211
|
+
try {
|
|
212
|
+
const newData = await this.fetchAllRepos();
|
|
213
|
+
this.repoCache.data = newData;
|
|
214
|
+
this.repoCache.lastFetched = Date.now();
|
|
215
|
+
} finally {
|
|
216
|
+
this.repoCache.isRefreshing = false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Retrieves the repository key for a given entity name.
|
|
221
|
+
* Uses a cached map that refreshes automatically when expired.
|
|
222
|
+
* @param entityName - The name of the entity/repository
|
|
223
|
+
* @returns Repository key or null if not found
|
|
224
|
+
*/
|
|
225
|
+
async getRepoKey(repoUrl) {
|
|
226
|
+
await this.refreshCacheIfNeeded();
|
|
227
|
+
return this.repoCache.data.get(repoUrl)?.key || null;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Creates an entity reference string in the format: kind:namespace/name
|
|
231
|
+
*/
|
|
232
|
+
createEntityReference(entity) {
|
|
233
|
+
const kind = entity.kind?.toLowerCase() || "component";
|
|
234
|
+
const namespace = entity.metadata.namespace || ApiiroAnnotationProcessor.DEFAULT_NAMESPACE;
|
|
235
|
+
const name = entity.metadata.name;
|
|
236
|
+
return `${kind}:${namespace}/${name}`;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Determines if an entity should have the metrics view annotation.
|
|
240
|
+
* Based on permission control configuration (exclude/include list).
|
|
241
|
+
*/
|
|
242
|
+
shouldAllowMetricsView(entity) {
|
|
243
|
+
const exclude = this.config.getOptionalBoolean("apiiro.annotationControl.exclude") ?? true;
|
|
244
|
+
const entityNames = this.config.getOptionalStringArray(
|
|
245
|
+
"apiiro.annotationControl.entityNames"
|
|
246
|
+
) ?? [];
|
|
247
|
+
if (entityNames.length === 0) {
|
|
248
|
+
return this.config.getOptionalBoolean("apiiro.defaultAllowMetricsView") ?? true;
|
|
249
|
+
}
|
|
250
|
+
const entityRef = this.createEntityReference(entity);
|
|
251
|
+
const isInList = entityNames.includes(entityRef);
|
|
252
|
+
return exclude ? !isInList : isInList;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Adds Apiiro annotations to an entity if they don't already exist.
|
|
256
|
+
*/
|
|
257
|
+
addApiiroAnnotations(entity, repoKey, allowMetricsView) {
|
|
258
|
+
const annotations = {
|
|
259
|
+
...entity.metadata?.annotations
|
|
260
|
+
};
|
|
261
|
+
if (repoKey && !Object.keys(annotations).includes(pluginApiiroCommon.APIIRO_PROJECT_ANNOTATION)) {
|
|
262
|
+
annotations[pluginApiiroCommon.APIIRO_PROJECT_ANNOTATION] = repoKey;
|
|
263
|
+
}
|
|
264
|
+
if ((repoKey || Object.keys(annotations).includes(pluginApiiroCommon.APIIRO_PROJECT_ANNOTATION)) && !Object.keys(annotations).includes(pluginApiiroCommon.APIIRO_METRICS_VIEW_ANNOTATION)) {
|
|
265
|
+
annotations[pluginApiiroCommon.APIIRO_METRICS_VIEW_ANNOTATION] = allowMetricsView ? "true" : "false";
|
|
266
|
+
}
|
|
267
|
+
return annotations;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Preprocesses an entity to add Apiiro-specific annotations.
|
|
271
|
+
* Only processes Component entities.
|
|
272
|
+
*/
|
|
273
|
+
async preProcessEntity(entity, _location, _emit, _originLocation, _cache) {
|
|
274
|
+
if (!this.shouldProcessEntity(entity)) {
|
|
275
|
+
return entity;
|
|
276
|
+
}
|
|
277
|
+
const allowMetricsView = this.shouldAllowMetricsView(entity);
|
|
278
|
+
const sourceLocation = entity.metadata.annotations?.[BACKSTAGE_SOURCE_LOCATION_ANNOTATION];
|
|
279
|
+
const repoUrl = this.extractRepoUrlFromSourceLocation(sourceLocation);
|
|
280
|
+
const repoKey = repoUrl ? await this.getRepoKey(repoUrl) : null;
|
|
281
|
+
const annotations = this.addApiiroAnnotations(
|
|
282
|
+
entity,
|
|
283
|
+
repoKey,
|
|
284
|
+
allowMetricsView
|
|
285
|
+
);
|
|
286
|
+
return {
|
|
287
|
+
...entity,
|
|
288
|
+
metadata: {
|
|
289
|
+
...entity.metadata,
|
|
290
|
+
annotations
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
exports.ApiiroAnnotationProcessor = ApiiroAnnotationProcessor;
|
|
297
|
+
//# sourceMappingURL=ApiiroAnnotationProcessor.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiiroAnnotationProcessor.cjs.js","sources":["../../src/processor/ApiiroAnnotationProcessor.ts"],"sourcesContent":["/*\n * Copyright 2025 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 {\n CatalogProcessor,\n CatalogProcessorEmit,\n CatalogProcessorCache,\n} from '@backstage/plugin-catalog-node';\nimport type { LocationSpec } from '@backstage/plugin-catalog-common';\nimport { Entity } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport fetch from 'node-fetch';\nimport {\n APIIRO_METRICS_VIEW_ANNOTATION,\n APIIRO_PROJECT_ANNOTATION,\n APIIRO_DEFAULT_BASE_URL,\n} from '@backstage-community/plugin-apiiro-common';\n\ninterface RepositoryItem {\n name?: string | null;\n key?: string | null;\n url?: string | null;\n isDefaultBranch?: boolean;\n branchName?: string | null;\n [key: string]: unknown;\n}\n\ninterface ApiiroRepositoriesResponse {\n items: RepositoryItem[];\n next?: string | null;\n}\n\ninterface RepoCache {\n data: Map<string, { key: string; isDefaultBranch: boolean }>;\n lastFetched: number;\n isRefreshing: boolean;\n}\n\nconst BACKSTAGE_SOURCE_LOCATION_ANNOTATION = 'backstage.io/source-location';\n\nexport class ApiiroAnnotationProcessor implements CatalogProcessor {\n private static readonly CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n private static readonly PAGE_LIMIT = 1000;\n private static readonly MAX_PAGES = 1000;\n private static readonly DEFAULT_NAMESPACE = 'default';\n\n private repoCache: RepoCache = {\n data: new Map(),\n lastFetched: 0,\n isRefreshing: false,\n };\n\n constructor(private readonly config: Config) {}\n\n getProcessorName(): string {\n return 'ApiiroAnnotationProcessor';\n }\n\n /**\n * Determines if an entity should be processed by this processor.\n * Only Component entities are processed.\n */\n private shouldProcessEntity(entity: Entity): boolean {\n return entity.kind === 'Component';\n }\n\n private extractRepoUrlFromSourceLocation(\n sourceLocation?: string,\n ): string | null {\n try {\n if (!sourceLocation) {\n return null;\n }\n\n const matches = sourceLocation.match(\n /https?:\\/\\/[a-zA-Z0-9\\-.]+\\.[a-zA-Z]{2,}(:[0-9]{1,5})?(\\/.*)?/g,\n );\n\n if (!matches) {\n return null;\n }\n\n const url = new URL(matches[0]);\n const hostname = url.host.toLowerCase();\n\n let repoPath: string | null = null;\n\n if (hostname === 'dev.azure.com') {\n const pathMatch = url.pathname.match(\n /^\\/([^/]+)\\/([^/]+)\\/_git\\/([^/]+)(?:\\/.*)?$/,\n );\n if (pathMatch) {\n repoPath = `/${pathMatch[1]}/${pathMatch[2]}/_git/${pathMatch[3]}`;\n }\n } else if (hostname.includes('gitlab')) {\n // GitLab can have nested subgroups: /group/subgroup1/subgroup2/.../project\n // Remove /-/... suffix first (GitLab file/blob paths), then extract repo path\n const cleanPath = url.pathname.replace(/\\/-\\/.*$/, '');\n const pathMatch = cleanPath.match(/^(\\/[^/]+\\/[^/]+(?:\\/[^/]+)*)$/);\n if (pathMatch) {\n repoPath = pathMatch[1];\n }\n } else {\n // GitHub and other providers: /org/repo\n const pathMatch = url.pathname.match(/^\\/([^/]+)\\/([^/]+)(?:\\/.*)?$/);\n if (pathMatch) {\n repoPath = `/${pathMatch[1]}/${pathMatch[2]}`;\n }\n }\n\n if (!repoPath) {\n return null;\n }\n\n return `${url.protocol}//${hostname}${repoPath}`;\n } catch (error) {\n return null;\n }\n }\n\n /**\n * Retrieves the Apiiro access token from configuration.\n * @throws {Error} If the access token is not configured\n */\n private getAccessToken(): string | undefined {\n const accessToken = this.config.getOptionalString('apiiro.accessToken');\n return accessToken;\n }\n\n /**\n * Fetches a single page of repositories from the Apiiro API.\n * @param pageCursor - Optional cursor for pagination\n * @returns Page of repositories with next cursor\n */\n private async fetchRepositoriesPage(\n accessToken: string,\n pageCursor?: string,\n ): Promise<ApiiroRepositoriesResponse> {\n const baseUrl = APIIRO_DEFAULT_BASE_URL;\n\n const params = new URLSearchParams();\n params.append('limit', ApiiroAnnotationProcessor.PAGE_LIMIT.toString());\n if (pageCursor) {\n params.append('next', pageCursor);\n }\n\n const url = `${baseUrl}/rest-api/v2/repositories?${params.toString()}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorMessage = `Failed to fetch repositories from Apiiro API. Status: ${response.status} ${response.statusText}`;\n throw new Error(errorMessage);\n }\n\n const data = (await response.json()) as ApiiroRepositoriesResponse;\n return {\n items: data.items || [],\n next: data.next || null,\n };\n }\n\n /**\n * Fetches all repositories from the Apiiro API with pagination.\n * @returns All repositories with total count\n * @throws {Error} If pagination limit is exceeded\n */\n private async fetchAllRepositories(): Promise<{\n items: RepositoryItem[];\n totalCount: number;\n }> {\n const items: RepositoryItem[] = [];\n let nextCursor: string | null | undefined = undefined;\n let pageCount = 0;\n\n do {\n pageCount++;\n\n if (pageCount > ApiiroAnnotationProcessor.MAX_PAGES) {\n throw new Error(\n `Pagination limit exceeded: Maximum of ${ApiiroAnnotationProcessor.MAX_PAGES} pages allowed. ` +\n `This may indicate an infinite loop or an unexpectedly large dataset. ` +\n `Fetched ${items.length} repositories so far.`,\n );\n }\n\n const accessToken = this.getAccessToken();\n if (!accessToken) {\n console.warn(\n '[ApiiroAnnotationProcessor] Apiiro access token not configured. Please set apiiro.accessToken in your app-config.',\n );\n return {\n items: [],\n totalCount: 0,\n };\n }\n const page = await this.fetchRepositoriesPage(\n accessToken,\n nextCursor ?? undefined,\n );\n items.push(...page.items);\n nextCursor = page.next;\n } while (nextCursor);\n\n return { items, totalCount: items.length };\n }\n\n /**\n * Optimized method that combines default branch selection and map building in a single loop.\n * Groups repositories by URL, selects the best branch for each, and builds the URL->key mapping.\n * @param repositories Array of all repository items\n * @returns Map of repository URLs to repository keys\n */\n private buildRepositoryMapWithDefaultBranches(\n repositories: RepositoryItem[],\n ): Map<string, { key: string; isDefaultBranch: boolean }> {\n const repoMap = new Map<\n string,\n { key: string; isDefaultBranch: boolean }\n >();\n\n // Single loop: Group repositories by URL\n for (const repo of repositories) {\n if (!repo.url) {\n continue; // Skip items without URL\n }\n if (repoMap.has(repo.url)) {\n if (repo.isDefaultBranch) {\n repoMap.set(repo.url, {\n key: repo.key!,\n isDefaultBranch: repo.isDefaultBranch!,\n });\n }\n continue;\n } else {\n repoMap.set(repo.url, {\n key: repo.key!,\n isDefaultBranch: repo.isDefaultBranch!,\n });\n }\n }\n\n return repoMap;\n }\n\n /**\n * Fetches all repositories and builds a name->key mapping.\n * Automatically filters to default branches only.\n * @returns Map of repository names to repository keys\n */\n private async fetchAllRepos(): Promise<\n Map<string, { key: string; isDefaultBranch: boolean }>\n > {\n try {\n // Fetch all repositories with pagination\n const { items } = await this.fetchAllRepositories();\n\n // Combine default branch selection and map building in a single operation\n return this.buildRepositoryMapWithDefaultBranches(items);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n console.error(\n `[ApiiroAnnotationProcessor] Error fetching repositories from Apiiro API: ${errorMessage}`,\n );\n return new Map();\n }\n }\n\n /**\n * Checks if the cache is expired.\n */\n private isCacheExpired(): boolean {\n const now = Date.now();\n return (\n now - this.repoCache.lastFetched >\n ApiiroAnnotationProcessor.CACHE_TTL_MS || this.repoCache.data.size === 0\n );\n }\n\n /**\n * Refreshes the repository cache from the Apiiro API.\n * Prevents concurrent refreshes using a flag.\n */\n private async refreshCacheIfNeeded(): Promise<void> {\n if (!this.isCacheExpired() || this.repoCache.isRefreshing) {\n return;\n }\n\n this.repoCache.isRefreshing = true;\n\n try {\n const newData = await this.fetchAllRepos();\n this.repoCache.data = newData;\n this.repoCache.lastFetched = Date.now();\n } finally {\n this.repoCache.isRefreshing = false;\n }\n }\n\n /**\n * Retrieves the repository key for a given entity name.\n * Uses a cached map that refreshes automatically when expired.\n * @param entityName - The name of the entity/repository\n * @returns Repository key or null if not found\n */\n private async getRepoKey(repoUrl: string): Promise<string | null> {\n await this.refreshCacheIfNeeded();\n return this.repoCache.data.get(repoUrl)?.key || null;\n }\n\n /**\n * Creates an entity reference string in the format: kind:namespace/name\n */\n private createEntityReference(entity: Entity): string {\n const kind = entity.kind?.toLowerCase() || 'component';\n const namespace =\n entity.metadata.namespace || ApiiroAnnotationProcessor.DEFAULT_NAMESPACE;\n const name = entity.metadata.name;\n return `${kind}:${namespace}/${name}`;\n }\n\n /**\n * Determines if an entity should have the metrics view annotation.\n * Based on permission control configuration (exclude/include list).\n */\n private shouldAllowMetricsView(entity: Entity): boolean {\n const exclude =\n this.config.getOptionalBoolean('apiiro.annotationControl.exclude') ??\n true;\n const entityNames =\n this.config.getOptionalStringArray(\n 'apiiro.annotationControl.entityNames',\n ) ?? [];\n\n if (entityNames.length === 0) {\n return (\n this.config.getOptionalBoolean('apiiro.defaultAllowMetricsView') ?? true\n );\n }\n\n const entityRef = this.createEntityReference(entity);\n const isInList = entityNames.includes(entityRef);\n\n // If exclude=true: allow all except those in list\n // If exclude=false: allow only those in list\n return exclude ? !isInList : isInList;\n }\n\n /**\n * Adds Apiiro annotations to an entity if they don't already exist.\n */\n private addApiiroAnnotations(\n entity: Entity,\n repoKey: string | null,\n allowMetricsView: boolean,\n ): Record<string, string> {\n const annotations: Record<string, string> = {\n ...entity.metadata?.annotations,\n };\n\n // Add project annotation if repo key exists and annotation not already set\n if (\n repoKey &&\n !Object.keys(annotations).includes(APIIRO_PROJECT_ANNOTATION)\n ) {\n annotations[APIIRO_PROJECT_ANNOTATION] = repoKey;\n }\n\n // Add metrics view annotation if allowed and not already set\n if (\n (repoKey ||\n Object.keys(annotations).includes(APIIRO_PROJECT_ANNOTATION)) &&\n !Object.keys(annotations).includes(APIIRO_METRICS_VIEW_ANNOTATION)\n ) {\n annotations[APIIRO_METRICS_VIEW_ANNOTATION] = allowMetricsView\n ? 'true'\n : 'false';\n }\n\n return annotations;\n }\n\n /**\n * Preprocesses an entity to add Apiiro-specific annotations.\n * Only processes Component entities.\n */\n async preProcessEntity(\n entity: Entity,\n _location: LocationSpec,\n _emit: CatalogProcessorEmit,\n _originLocation: LocationSpec,\n _cache: CatalogProcessorCache,\n ): Promise<Entity> {\n if (!this.shouldProcessEntity(entity)) {\n return entity;\n }\n\n // Determine if metrics view should be allowed\n const allowMetricsView = this.shouldAllowMetricsView(entity);\n\n const sourceLocation =\n entity.metadata.annotations?.[BACKSTAGE_SOURCE_LOCATION_ANNOTATION];\n const repoUrl = this.extractRepoUrlFromSourceLocation(sourceLocation);\n\n // Get repository key from cache (refreshes automatically if needed)\n const repoKey = repoUrl ? await this.getRepoKey(repoUrl) : null;\n\n // Add Apiiro annotations\n const annotations = this.addApiiroAnnotations(\n entity,\n repoKey,\n allowMetricsView,\n );\n\n return {\n ...entity,\n metadata: {\n ...entity.metadata,\n annotations,\n },\n };\n }\n}\n"],"names":["APIIRO_DEFAULT_BASE_URL","fetch","APIIRO_PROJECT_ANNOTATION","APIIRO_METRICS_VIEW_ANNOTATION"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@backstage-community/plugin-catalog-backend-module-apiiro-entity-processor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"description": "The apiiro-entity-processor backend module for the catalog plugin.",
|
|
6
|
+
"main": "dist/index.cjs.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"main": "dist/index.cjs.js",
|
|
11
|
+
"types": "dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/backstage/community-plugins",
|
|
16
|
+
"directory": "workspaces/apiiro/plugins/catalog-backend-module-apiiro-entity-processor"
|
|
17
|
+
},
|
|
18
|
+
"backstage": {
|
|
19
|
+
"role": "backend-plugin-module",
|
|
20
|
+
"pluginId": "catalog",
|
|
21
|
+
"pluginPackage": "@backstage/plugin-catalog-backend",
|
|
22
|
+
"features": {
|
|
23
|
+
".": "@backstage/BackendFeature"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"start": "backstage-cli package start",
|
|
28
|
+
"build": "backstage-cli package build",
|
|
29
|
+
"lint": "backstage-cli package lint",
|
|
30
|
+
"test": "backstage-cli package test",
|
|
31
|
+
"clean": "backstage-cli package clean",
|
|
32
|
+
"prepack": "backstage-cli package prepack",
|
|
33
|
+
"postpack": "backstage-cli package postpack"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@backstage-community/plugin-apiiro-common": "^0.1.0",
|
|
37
|
+
"@backstage/backend-plugin-api": "^1.4.4",
|
|
38
|
+
"@backstage/catalog-model": "^1.7.5",
|
|
39
|
+
"@backstage/config": "^1.3.5",
|
|
40
|
+
"@backstage/plugin-catalog-common": "^1.1.6",
|
|
41
|
+
"@backstage/plugin-catalog-node": "^1.19.1",
|
|
42
|
+
"node-fetch": "^2.6.7"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@backstage/cli": "^0.34.4",
|
|
46
|
+
"@types/node-fetch": "^2.6.9"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist"
|
|
50
|
+
],
|
|
51
|
+
"typesVersions": {
|
|
52
|
+
"*": {
|
|
53
|
+
"package.json": [
|
|
54
|
+
"package.json"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|