@red-hat-developer-hub/backstage-plugin-scorecard-backend 2.1.0 → 2.3.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 +88 -0
- package/README.md +105 -4
- package/dist/database/DatabaseMetricValues.cjs.js +37 -0
- package/dist/database/DatabaseMetricValues.cjs.js.map +1 -1
- package/dist/permissions/permissionUtils.cjs.js +1 -1
- package/dist/permissions/permissionUtils.cjs.js.map +1 -1
- package/dist/plugin.cjs.js +3 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/providers/MetricProvidersRegistry.cjs.js +9 -3
- package/dist/providers/MetricProvidersRegistry.cjs.js.map +1 -1
- package/dist/scheduler/index.cjs.js +4 -1
- package/dist/scheduler/index.cjs.js.map +1 -1
- package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js +17 -3
- package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js.map +1 -1
- package/dist/service/CatalogMetricService.cjs.js +79 -102
- package/dist/service/CatalogMetricService.cjs.js.map +1 -1
- package/dist/service/router.cjs.js +57 -28
- package/dist/service/router.cjs.js.map +1 -1
- package/dist/utils/getEntitiesOwnedByUser.cjs.js +49 -0
- package/dist/utils/getEntitiesOwnedByUser.cjs.js.map +1 -0
- package/dist/utils/mergeEntityAndProviderThresholds.cjs.js +59 -0
- package/dist/utils/mergeEntityAndProviderThresholds.cjs.js.map +1 -0
- package/dist/utils/parseCommaSeparatedString.cjs.js +8 -0
- package/dist/utils/parseCommaSeparatedString.cjs.js.map +1 -0
- package/dist/validation/validateCatalogMetricsSchema.cjs.js +18 -0
- package/dist/validation/validateCatalogMetricsSchema.cjs.js.map +1 -0
- package/dist/validation/validateMetricsSchema.cjs.js +19 -0
- package/dist/validation/validateMetricsSchema.cjs.js.map +1 -0
- package/migrations/20251117131443_add_status_column.js +30 -0
- package/package.json +13 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-scorecard-backend
|
|
2
2
|
|
|
3
|
+
## 2.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 23e21ad: Added `metricIds` query parameter to the `/metrics` endpoint to filter metrics by metric IDs.
|
|
8
|
+
|
|
9
|
+
Scorecard read permission are no longer needed to get available metrics for the `/metrics` endpoint.
|
|
10
|
+
|
|
11
|
+
- 4e360d5: Implemented endpoint to aggregate metrics for scorecard metrics
|
|
12
|
+
|
|
13
|
+
**BREAKING** Update attribute `value` in the `MetricResult` type and update validation to support `null` instead `undefined` for the updated attribute
|
|
14
|
+
|
|
15
|
+
```diff
|
|
16
|
+
export type MetricResult = {
|
|
17
|
+
id: string;
|
|
18
|
+
status: 'success' | 'error';
|
|
19
|
+
metadata: {
|
|
20
|
+
title: string;
|
|
21
|
+
description: string;
|
|
22
|
+
type: MetricType;
|
|
23
|
+
history?: boolean;
|
|
24
|
+
};
|
|
25
|
+
result: {
|
|
26
|
+
- value?: MetricValue;
|
|
27
|
+
+ value: MetricValue | null;
|
|
28
|
+
timestamp: string;
|
|
29
|
+
thresholdResult: ThresholdResult;
|
|
30
|
+
};
|
|
31
|
+
error?: string;
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**BREAKING** Update attribute `evaluation` in the `ThresholdResult` type and update validation to support `null` instead `undefined` for the updated attribute
|
|
36
|
+
|
|
37
|
+
```diff
|
|
38
|
+
export type ThresholdResult = {
|
|
39
|
+
status: 'success' | 'error';
|
|
40
|
+
- definition: ThresholdConfig | undefined;
|
|
41
|
+
+ definition: ThresholdConfig | null;
|
|
42
|
+
evaluation: string | undefined; // threshold key the expression evaluated to
|
|
43
|
+
error?: string;
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Patch Changes
|
|
48
|
+
|
|
49
|
+
- Updated dependencies [52b60ee]
|
|
50
|
+
- Updated dependencies [4e360d5]
|
|
51
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-common@2.3.0
|
|
52
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-node@2.3.0
|
|
53
|
+
|
|
54
|
+
## 2.2.0
|
|
55
|
+
|
|
56
|
+
### Minor Changes
|
|
57
|
+
|
|
58
|
+
- f8fb8e4: Implemented saving metric `status` to the database. Added logic for saving `status` in the metric puller scheduler.
|
|
59
|
+
|
|
60
|
+
**BREAKING**: Added method `getMetricType` to the `MetricProvider` interface and updated the `getMetric` method to use `getMetricType()` instead of hardcoded `type` values.
|
|
61
|
+
|
|
62
|
+
```diff
|
|
63
|
+
export class MyMetricProvider implements MetricProvider {
|
|
64
|
+
+ getMetricType(): 'number' {
|
|
65
|
+
+ return 'number';
|
|
66
|
+
+ }
|
|
67
|
+
|
|
68
|
+
getMetric(): Metric<'number'> {
|
|
69
|
+
return {
|
|
70
|
+
id: this.getProviderId(),
|
|
71
|
+
title: 'GitHub open PRs',
|
|
72
|
+
description:
|
|
73
|
+
'Current count of open Pull Requests for a given GitHub repository.',
|
|
74
|
+
- type: 'number',
|
|
75
|
+
+ type: this.getMetricType(),
|
|
76
|
+
history: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- 4c2261f: Backstage version bump to v1.45.2
|
|
83
|
+
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- Updated dependencies [f8fb8e4]
|
|
87
|
+
- Updated dependencies [4c2261f]
|
|
88
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-node@2.2.0
|
|
89
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-common@2.2.0
|
|
90
|
+
|
|
3
91
|
## 2.1.0
|
|
4
92
|
|
|
5
93
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -75,15 +75,17 @@ The Scorecard plugin collects metrics from third-party data sources using metric
|
|
|
75
75
|
|
|
76
76
|
The following metric providers are available:
|
|
77
77
|
|
|
78
|
-
| Provider
|
|
79
|
-
|
|
|
80
|
-
| **GitHub**
|
|
81
|
-
| **Jira**
|
|
78
|
+
| Provider | Metric ID | Title | Description | Type |
|
|
79
|
+
| ----------- | ------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------ |
|
|
80
|
+
| **GitHub** | `github.open_prs` | GitHub open PRs | Count of open Pull Requests in GitHub | number |
|
|
81
|
+
| **Jira** | `jira.open_issues` | Jira open issues | The number of opened issues in Jira | number |
|
|
82
|
+
| **OpenSSF** | `openssf.*` | OpenSSF Security Scorecards | 18 security metrics from OpenSSF Scorecards (e.g., `openssf.code_review`, `openssf.maintained`). Each returns a score from 0-10. | number |
|
|
82
83
|
|
|
83
84
|
To use these providers, install the corresponding backend modules:
|
|
84
85
|
|
|
85
86
|
- GitHub: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-github`](../scorecard-backend-module-github/README.md)
|
|
86
87
|
- Jira: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira`](../scorecard-backend-module-jira/README.md)
|
|
88
|
+
- OpenSSF: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-openssf`](../scorecard-backend-module-openssf/README.md)
|
|
87
89
|
|
|
88
90
|
## Thresholds
|
|
89
91
|
|
|
@@ -97,6 +99,105 @@ Thresholds are evaluated in order, and the first matching rule determines the ca
|
|
|
97
99
|
|
|
98
100
|
For comprehensive threshold configuration guide, examples, and best practices, see [thresholds.md](./docs/thresholds.md).
|
|
99
101
|
|
|
102
|
+
## API Endpoints
|
|
103
|
+
|
|
104
|
+
### `GET /metrics`
|
|
105
|
+
|
|
106
|
+
Returns a list of available metrics. Supports filtering by metric IDs or datasource.
|
|
107
|
+
|
|
108
|
+
#### Query Parameters
|
|
109
|
+
|
|
110
|
+
| Parameter | Type | Required | Description |
|
|
111
|
+
| ------------ | ------ | -------- | -------------------------------------------------------------------------------------------- |
|
|
112
|
+
| `metricIds` | string | No | Comma-separated list of metric IDs to filter by (e.g., `github.open_prs,github.open_issues`) |
|
|
113
|
+
| `datasource` | string | No | Filter metrics by datasource ID (e.g., `github`, `jira`, `sonar`) |
|
|
114
|
+
|
|
115
|
+
#### Behavior
|
|
116
|
+
|
|
117
|
+
- If `metricIds` is provided, returns only the specified metrics
|
|
118
|
+
- If `datasource` is provided (and `metricIds` is not), returns all metrics from that datasource
|
|
119
|
+
- If neither parameter is provided, returns all available metrics
|
|
120
|
+
- **Note**: Providing both `metricIds` and `datasource` will result in a `400 Bad Request` error
|
|
121
|
+
|
|
122
|
+
#### Example Requests
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Get all metrics
|
|
126
|
+
curl -X GET "{{url}}/api/scorecard/metrics" \
|
|
127
|
+
-H "Authorization: Bearer <token>"
|
|
128
|
+
|
|
129
|
+
# Get specific metrics by IDs
|
|
130
|
+
curl -X GET "{{url}}/api/scorecard/metrics?metricIds=github.open_prs,github.open_issues" \
|
|
131
|
+
-H "Authorization: Bearer <token>"
|
|
132
|
+
|
|
133
|
+
# Get all metrics from a specific datasource
|
|
134
|
+
curl -X GET "{{url}}/api/scorecard/metrics?datasource=github" \
|
|
135
|
+
-H "Authorization: Bearer <token>"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `GET /metrics/catalog/:kind/:namespace/:name`
|
|
139
|
+
|
|
140
|
+
Returns the latest metric values for a specific catalog entity.
|
|
141
|
+
|
|
142
|
+
#### Path Parameters
|
|
143
|
+
|
|
144
|
+
| Parameter | Type | Required | Description |
|
|
145
|
+
| ----------- | ------ | -------- | ---------------------------------- |
|
|
146
|
+
| `kind` | string | Yes | Entity kind (e.g., `component`) |
|
|
147
|
+
| `namespace` | string | Yes | Entity namespace (e.g., `default`) |
|
|
148
|
+
| `name` | string | Yes | Entity name |
|
|
149
|
+
|
|
150
|
+
#### Query Parameters
|
|
151
|
+
|
|
152
|
+
| Parameter | Type | Required | Description |
|
|
153
|
+
| ----------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
|
|
154
|
+
| `metricIds` | string | No | Comma-separated list of metric IDs to filter by (e.g., `github.open_prs,github.open_issues`) |
|
|
155
|
+
|
|
156
|
+
#### Permissions
|
|
157
|
+
|
|
158
|
+
Requires `scorecard.metric.read` permission and `catalog.entity.read` permission for the specific entity.
|
|
159
|
+
|
|
160
|
+
#### Example Request
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
curl -X GET "{{url}}/api/scorecard/metrics/catalog/component/default/my-service?metricIds=github.open_prs" \
|
|
164
|
+
-H "Authorization: Bearer <token>"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### `GET /metrics/:metricId/catalog/aggregations`
|
|
168
|
+
|
|
169
|
+
Returns aggregated metrics for a specific metric across all entities owned by the authenticated user. This endpoint aggregates metrics from:
|
|
170
|
+
|
|
171
|
+
- Entities directly owned by the user
|
|
172
|
+
- Entities owned by groups the user is a direct member of (only direct parent groups are considered)
|
|
173
|
+
|
|
174
|
+
#### Path Parameters
|
|
175
|
+
|
|
176
|
+
| Parameter | Type | Required | Description |
|
|
177
|
+
| ---------- | ------ | -------- | --------------------------------- |
|
|
178
|
+
| `metricId` | string | Yes | The ID of the metric to aggregate |
|
|
179
|
+
|
|
180
|
+
#### Authentication
|
|
181
|
+
|
|
182
|
+
Requires user authentication. The endpoint uses the authenticated user's entity reference to determine which entities to aggregate.
|
|
183
|
+
|
|
184
|
+
#### Permissions
|
|
185
|
+
|
|
186
|
+
Requires `scorecard.metric.read` permission. Additionally:
|
|
187
|
+
|
|
188
|
+
- The user must have access to the specific metric (returns `403 Forbidden` if access is denied)
|
|
189
|
+
- The user must have `catalog.entity.read` permission for each entity that will be included in the aggregation
|
|
190
|
+
|
|
191
|
+
#### Example Request
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Get aggregated metrics for a specific metric
|
|
195
|
+
curl -X GET "{{url}}/api/scorecard/metrics/github.open_prs/catalog/aggregations" \
|
|
196
|
+
-H "Authorization: Bearer <token>"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
For comprehensive documentation on how entity aggregation works, including details on transitive parent groups, error handling, and best practices, see [aggregation.md](./docs/aggregation.md).
|
|
200
|
+
|
|
100
201
|
## Configuration cleanup Job
|
|
101
202
|
|
|
102
203
|
The plugin has a predefined job that runs every day to check and clean old metrics. By default, metrics are saved for **365 days**, however, this period can be changed in the `app-config.yaml` file. Here is an example of how to do that:
|
|
@@ -26,6 +26,43 @@ class DatabaseMetricValues {
|
|
|
26
26
|
async cleanupExpiredMetrics(olderThan) {
|
|
27
27
|
return await this.dbClient(this.tableName).where("timestamp", "<", olderThan).del();
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Get aggregated metrics by status for multiple entities and metrics.
|
|
31
|
+
*/
|
|
32
|
+
async readAggregatedMetricsByEntityRefs(catalog_entity_refs, metric_ids) {
|
|
33
|
+
const latestIdsSubquery = this.dbClient(this.tableName).max("id").whereIn("metric_id", metric_ids).whereIn("catalog_entity_ref", catalog_entity_refs).groupBy("metric_id", "catalog_entity_ref");
|
|
34
|
+
const results = await this.dbClient(this.tableName).select("metric_id").count("* as total").max("timestamp as max_timestamp").select(
|
|
35
|
+
this.dbClient.raw(
|
|
36
|
+
"SUM(CASE WHEN status = 'success' AND value IS NOT NULL THEN 1 ELSE 0 END) as success"
|
|
37
|
+
)
|
|
38
|
+
).select(
|
|
39
|
+
this.dbClient.raw(
|
|
40
|
+
"SUM(CASE WHEN status = 'warning' AND value IS NOT NULL THEN 1 ELSE 0 END) as warning"
|
|
41
|
+
)
|
|
42
|
+
).select(
|
|
43
|
+
this.dbClient.raw(
|
|
44
|
+
"SUM(CASE WHEN status = 'error' AND value IS NOT NULL THEN 1 ELSE 0 END) as error"
|
|
45
|
+
)
|
|
46
|
+
).whereIn("id", latestIdsSubquery).whereNotNull("status").whereNotNull("value").groupBy("metric_id");
|
|
47
|
+
return results.map((row) => {
|
|
48
|
+
let maxTimestamp;
|
|
49
|
+
if (row.max_timestamp instanceof Date) {
|
|
50
|
+
maxTimestamp = row.max_timestamp;
|
|
51
|
+
} else if (typeof row.max_timestamp === "number" || typeof row.max_timestamp === "string") {
|
|
52
|
+
maxTimestamp = new Date(row.max_timestamp);
|
|
53
|
+
} else {
|
|
54
|
+
maxTimestamp = /* @__PURE__ */ new Date();
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
metric_id: row.metric_id,
|
|
58
|
+
total: Number(row.total),
|
|
59
|
+
max_timestamp: maxTimestamp,
|
|
60
|
+
success: Number(row.success),
|
|
61
|
+
warning: Number(row.warning),
|
|
62
|
+
error: Number(row.error)
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
29
66
|
}
|
|
30
67
|
|
|
31
68
|
exports.DatabaseMetricValues = DatabaseMetricValues;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatabaseMetricValues.cjs.js","sources":["../../src/database/DatabaseMetricValues.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 { Knex } from 'knex';\nimport {
|
|
1
|
+
{"version":3,"file":"DatabaseMetricValues.cjs.js","sources":["../../src/database/DatabaseMetricValues.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 { Knex } from 'knex';\nimport {\n DbMetricValueCreate,\n DbMetricValue,\n DbAggregatedMetric,\n} from './types';\n\nexport class DatabaseMetricValues {\n private readonly tableName = 'metric_values';\n\n constructor(private readonly dbClient: Knex<any, any[]>) {}\n\n /**\n * Insert multiple metric values\n */\n async createMetricValues(metricValues: DbMetricValueCreate[]): Promise<void> {\n await this.dbClient(this.tableName).insert(metricValues);\n }\n\n /**\n * Get the latest metric values for a specific entity and metrics\n */\n async readLatestEntityMetricValues(\n catalog_entity_ref: string,\n metric_ids: string[],\n ): Promise<DbMetricValue[]> {\n return await this.dbClient(this.tableName)\n .select('*')\n .whereIn(\n 'id',\n this.dbClient(this.tableName)\n .max('id')\n .whereIn('metric_id', metric_ids)\n .where('catalog_entity_ref', catalog_entity_ref)\n .groupBy('metric_id'),\n );\n }\n\n /**\n * Delete metric values that are older than the given date\n */\n async cleanupExpiredMetrics(olderThan: Date): Promise<number> {\n return await this.dbClient(this.tableName)\n .where('timestamp', '<', olderThan)\n .del();\n }\n\n /**\n * Get aggregated metrics by status for multiple entities and metrics.\n */\n async readAggregatedMetricsByEntityRefs(\n catalog_entity_refs: string[],\n metric_ids: string[],\n ): Promise<DbAggregatedMetric[]> {\n const latestIdsSubquery = this.dbClient(this.tableName)\n .max('id')\n .whereIn('metric_id', metric_ids)\n .whereIn('catalog_entity_ref', catalog_entity_refs)\n .groupBy('metric_id', 'catalog_entity_ref');\n\n const results = await this.dbClient(this.tableName)\n .select('metric_id')\n .count('* as total')\n .max('timestamp as max_timestamp')\n .select(\n this.dbClient.raw(\n \"SUM(CASE WHEN status = 'success' AND value IS NOT NULL THEN 1 ELSE 0 END) as success\",\n ),\n )\n .select(\n this.dbClient.raw(\n \"SUM(CASE WHEN status = 'warning' AND value IS NOT NULL THEN 1 ELSE 0 END) as warning\",\n ),\n )\n .select(\n this.dbClient.raw(\n \"SUM(CASE WHEN status = 'error' AND value IS NOT NULL THEN 1 ELSE 0 END) as error\",\n ),\n )\n .whereIn('id', latestIdsSubquery)\n .whereNotNull('status')\n .whereNotNull('value')\n .groupBy('metric_id');\n\n // Normalize types for cross-database compatibility\n // PostgreSQL returns COUNT/SUM as strings, SQLite returns numbers\n // PostgreSQL returns MAX(timestamp) as Date, SQLite returns number (milliseconds)\n return results.map(row => {\n let maxTimestamp: Date;\n if (row.max_timestamp instanceof Date) {\n maxTimestamp = row.max_timestamp;\n } else if (\n typeof row.max_timestamp === 'number' ||\n typeof row.max_timestamp === 'string'\n ) {\n maxTimestamp = new Date(row.max_timestamp as string | number);\n } else {\n maxTimestamp = new Date();\n }\n\n return {\n metric_id: row.metric_id,\n total: Number(row.total),\n max_timestamp: maxTimestamp,\n success: Number(row.success),\n warning: Number(row.warning),\n error: Number(row.error),\n };\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ const checkEntityAccess = async (entityRef, req, permissions, httpAuth) => {
|
|
|
11
11
|
{ credentials: await httpAuth.credentials(req) }
|
|
12
12
|
);
|
|
13
13
|
if (entityAccessDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
14
|
-
throw new errors.NotAllowedError(
|
|
14
|
+
throw new errors.NotAllowedError(`Access to "${entityRef}" entity metrics denied`);
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
17
|
const matches = (metric, filters) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissionUtils.cjs.js","sources":["../../src/permissions/permissionUtils.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n AuthorizeResult,\n} from '@backstage/plugin-permission-common';\nimport { Request } from 'express';\nimport { NotAllowedError } from '@backstage/errors';\nimport { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';\nimport type {\n HttpAuthService,\n PermissionsService,\n} from '@backstage/backend-plugin-api';\n\nimport { Metric } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nimport { rules as scorecardRules } from './rules';\n\nexport const checkEntityAccess = async (\n entityRef: string,\n req: Request,\n permissions: PermissionsService,\n httpAuth: HttpAuthService,\n): Promise<void> => {\n const entityAccessDecision = await permissions.authorize(\n [{ permission: catalogEntityReadPermission, resourceRef: entityRef }],\n { credentials: await httpAuth.credentials(req) },\n );\n\n if (entityAccessDecision[0].result !== AuthorizeResult.ALLOW) {\n throw new NotAllowedError(
|
|
1
|
+
{"version":3,"file":"permissionUtils.cjs.js","sources":["../../src/permissions/permissionUtils.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n AuthorizeResult,\n} from '@backstage/plugin-permission-common';\nimport { Request } from 'express';\nimport { NotAllowedError } from '@backstage/errors';\nimport { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';\nimport type {\n HttpAuthService,\n PermissionsService,\n} from '@backstage/backend-plugin-api';\n\nimport { Metric } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nimport { rules as scorecardRules } from './rules';\n\nexport const checkEntityAccess = async (\n entityRef: string,\n req: Request,\n permissions: PermissionsService,\n httpAuth: HttpAuthService,\n): Promise<void> => {\n const entityAccessDecision = await permissions.authorize(\n [{ permission: catalogEntityReadPermission, resourceRef: entityRef }],\n { credentials: await httpAuth.credentials(req) },\n );\n\n if (entityAccessDecision[0].result !== AuthorizeResult.ALLOW) {\n throw new NotAllowedError(`Access to \"${entityRef}\" entity metrics denied`);\n }\n};\n\nexport const matches = (\n metric: Metric,\n filters?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n): boolean => {\n if (!filters) {\n return true;\n }\n\n if ('allOf' in filters) {\n return filters.allOf.every(filter => matches(metric, filter));\n }\n\n if ('anyOf' in filters) {\n return filters.anyOf.some(filter => matches(metric, filter));\n }\n\n if ('not' in filters) {\n return !matches(metric, filters.not);\n }\n return (\n Object.values(scorecardRules)\n .find(r => r.name === filters.rule)\n ?.apply(metric, filters.params ?? {}) ?? false\n );\n};\n\nexport const filterAuthorizedMetrics = (\n metrics: Metric[],\n filter?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n) => {\n if (!filter) {\n return metrics;\n }\n\n return metrics.filter(metric => matches(metric, filter));\n};\n"],"names":["catalogEntityReadPermission","AuthorizeResult","NotAllowedError","scorecardRules"],"mappings":";;;;;;;AAkCO,MAAM,iBAAoB,GAAA,OAC/B,SACA,EAAA,GAAA,EACA,aACA,QACkB,KAAA;AAClB,EAAM,MAAA,oBAAA,GAAuB,MAAM,WAAY,CAAA,SAAA;AAAA,IAC7C,CAAC,EAAE,UAAA,EAAYA,iCAA6B,EAAA,WAAA,EAAa,WAAW,CAAA;AAAA,IACpE,EAAE,WAAa,EAAA,MAAM,QAAS,CAAA,WAAA,CAAY,GAAG,CAAE;AAAA,GACjD;AAEA,EAAA,IAAI,oBAAqB,CAAA,CAAC,CAAE,CAAA,MAAA,KAAWC,uCAAgB,KAAO,EAAA;AAC5D,IAAA,MAAM,IAAIC,sBAAA,CAAgB,CAAc,WAAA,EAAA,SAAS,CAAyB,uBAAA,CAAA,CAAA;AAAA;AAE9E;AAEa,MAAA,OAAA,GAAU,CACrB,MAAA,EACA,OAGY,KAAA;AACZ,EAAA,IAAI,CAAC,OAAS,EAAA;AACZ,IAAO,OAAA,IAAA;AAAA;AAGT,EAAA,IAAI,WAAW,OAAS,EAAA;AACtB,IAAA,OAAO,QAAQ,KAAM,CAAA,KAAA,CAAM,YAAU,OAAQ,CAAA,MAAA,EAAQ,MAAM,CAAC,CAAA;AAAA;AAG9D,EAAA,IAAI,WAAW,OAAS,EAAA;AACtB,IAAA,OAAO,QAAQ,KAAM,CAAA,IAAA,CAAK,YAAU,OAAQ,CAAA,MAAA,EAAQ,MAAM,CAAC,CAAA;AAAA;AAG7D,EAAA,IAAI,SAAS,OAAS,EAAA;AACpB,IAAA,OAAO,CAAC,OAAA,CAAQ,MAAQ,EAAA,OAAA,CAAQ,GAAG,CAAA;AAAA;AAErC,EAAA,OACE,OAAO,MAAO,CAAAC,WAAc,CACzB,CAAA,IAAA,CAAK,OAAK,CAAE,CAAA,IAAA,KAAS,OAAQ,CAAA,IAAI,GAChC,KAAM,CAAA,MAAA,EAAQ,QAAQ,MAAU,IAAA,EAAE,CAAK,IAAA,KAAA;AAE/C;AAEa,MAAA,uBAAA,GAA0B,CACrC,OAAA,EACA,MAGG,KAAA;AACH,EAAA,IAAI,CAAC,MAAQ,EAAA;AACX,IAAO,OAAA,OAAA;AAAA;AAGT,EAAA,OAAO,QAAQ,MAAO,CAAA,CAAA,MAAA,KAAU,OAAQ,CAAA,MAAA,EAAQ,MAAM,CAAC,CAAA;AACzD;;;;;;"}
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -64,7 +64,6 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
|
|
|
64
64
|
catalog,
|
|
65
65
|
auth,
|
|
66
66
|
registry: metricProvidersRegistry,
|
|
67
|
-
thresholdEvaluator: new ThresholdEvaluator.ThresholdEvaluator(),
|
|
68
67
|
database: dbMetricValues
|
|
69
68
|
});
|
|
70
69
|
index.Scheduler.create({
|
|
@@ -74,12 +73,14 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
|
|
|
74
73
|
logger,
|
|
75
74
|
scheduler,
|
|
76
75
|
database: dbMetricValues,
|
|
77
|
-
metricProvidersRegistry
|
|
76
|
+
metricProvidersRegistry,
|
|
77
|
+
thresholdEvaluator: new ThresholdEvaluator.ThresholdEvaluator()
|
|
78
78
|
}).start();
|
|
79
79
|
httpRouter.use(
|
|
80
80
|
await router.createRouter({
|
|
81
81
|
metricProvidersRegistry,
|
|
82
82
|
catalogMetricService,
|
|
83
|
+
catalog,
|
|
83
84
|
httpAuth,
|
|
84
85
|
permissions
|
|
85
86
|
})
|
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport {\n MetricProvider,\n scorecardMetricsExtensionPoint,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { MetricProvidersRegistry } from './providers/MetricProvidersRegistry';\nimport { CatalogMetricService } from './service/CatalogMetricService';\nimport { ThresholdEvaluator } from './threshold/ThresholdEvaluator';\nimport { scorecardPermissions } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n scorecardMetricPermissionResourceRef,\n rules as scorecardRules,\n} from './permissions/rules';\nimport { migrate } from './database/migration';\nimport { DatabaseMetricValues } from './database/DatabaseMetricValues';\nimport { Scheduler } from './scheduler';\n\n/**\n * scorecardPlugin backend plugin\n *\n * @public\n */\nexport const scorecardPlugin = createBackendPlugin({\n pluginId: 'scorecard',\n register(env) {\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardMetricsExtensionPoint, {\n addMetricProvider(...newMetricProviders: MetricProvider[]) {\n newMetricProviders.forEach(metricProvider => {\n metricProvidersRegistry.register(metricProvider);\n });\n },\n });\n\n env.registerInit({\n deps: {\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n scheduler: coreServices.scheduler,\n },\n async init({\n auth,\n catalog,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n permissionsRegistry.addResourceType({\n resourceRef: scorecardMetricPermissionResourceRef,\n getResources: async (resourceRefs: string[]) => {\n return metricProvidersRegistry.listMetrics(resourceRefs);\n },\n permissions: scorecardPermissions,\n rules: Object.values(scorecardRules),\n });\n\n // Run database migrations\n await migrate(database);\n\n const client = await database.getClient();\n const dbMetricValues = new DatabaseMetricValues(client);\n\n const catalogMetricService = new CatalogMetricService({\n catalog,\n auth,\n registry: metricProvidersRegistry,\n
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport {\n MetricProvider,\n scorecardMetricsExtensionPoint,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { MetricProvidersRegistry } from './providers/MetricProvidersRegistry';\nimport { CatalogMetricService } from './service/CatalogMetricService';\nimport { ThresholdEvaluator } from './threshold/ThresholdEvaluator';\nimport { scorecardPermissions } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n scorecardMetricPermissionResourceRef,\n rules as scorecardRules,\n} from './permissions/rules';\nimport { migrate } from './database/migration';\nimport { DatabaseMetricValues } from './database/DatabaseMetricValues';\nimport { Scheduler } from './scheduler';\n\n/**\n * scorecardPlugin backend plugin\n *\n * @public\n */\nexport const scorecardPlugin = createBackendPlugin({\n pluginId: 'scorecard',\n register(env) {\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardMetricsExtensionPoint, {\n addMetricProvider(...newMetricProviders: MetricProvider[]) {\n newMetricProviders.forEach(metricProvider => {\n metricProvidersRegistry.register(metricProvider);\n });\n },\n });\n\n env.registerInit({\n deps: {\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n scheduler: coreServices.scheduler,\n },\n async init({\n auth,\n catalog,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n permissionsRegistry.addResourceType({\n resourceRef: scorecardMetricPermissionResourceRef,\n getResources: async (resourceRefs: string[]) => {\n return metricProvidersRegistry.listMetrics(resourceRefs);\n },\n permissions: scorecardPermissions,\n rules: Object.values(scorecardRules),\n });\n\n // Run database migrations\n await migrate(database);\n\n const client = await database.getClient();\n const dbMetricValues = new DatabaseMetricValues(client);\n\n const catalogMetricService = new CatalogMetricService({\n catalog,\n auth,\n registry: metricProvidersRegistry,\n database: dbMetricValues,\n });\n\n Scheduler.create({\n auth,\n catalog,\n config,\n logger,\n scheduler,\n database: dbMetricValues,\n metricProvidersRegistry,\n thresholdEvaluator: new ThresholdEvaluator(),\n }).start();\n\n httpRouter.use(\n await createRouter({\n metricProvidersRegistry,\n catalogMetricService,\n catalog,\n httpAuth,\n permissions,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","MetricProvidersRegistry","scorecardMetricsExtensionPoint","coreServices","catalogServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","CatalogMetricService","Scheduler","ThresholdEvaluator","createRouter"],"mappings":";;;;;;;;;;;;;;;AA0CO,MAAM,kBAAkBA,oCAAoB,CAAA;AAAA,EACjD,QAAU,EAAA,WAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAM,MAAA,uBAAA,GAA0B,IAAIC,+CAAwB,EAAA;AAE5D,IAAA,GAAA,CAAI,uBAAuBC,2DAAgC,EAAA;AAAA,MACzD,qBAAqB,kBAAsC,EAAA;AACzD,QAAA,kBAAA,CAAmB,QAAQ,CAAkB,cAAA,KAAA;AAC3C,UAAA,uBAAA,CAAwB,SAAS,cAAc,CAAA;AAAA,SAChD,CAAA;AAAA;AACH,KACD,CAAA;AAED,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,IAAA;AAAA,QACnB,OAAS,EAAAC,mCAAA;AAAA,QACT,QAAQD,6BAAa,CAAA,UAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,aAAaA,6BAAa,CAAA,WAAA;AAAA,QAC1B,qBAAqBA,6BAAa,CAAA,mBAAA;AAAA,QAClC,WAAWA,6BAAa,CAAA;AAAA,OAC1B;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,IAAA;AAAA,QACA,OAAA;AAAA,QACA,MAAA;AAAA,QACA,QAAA;AAAA,QACA,UAAA;AAAA,QACA,QAAA;AAAA,QACA,MAAA;AAAA,QACA,WAAA;AAAA,QACA,mBAAA;AAAA,QACA;AAAA,OACC,EAAA;AACD,QAAA,mBAAA,CAAoB,eAAgB,CAAA;AAAA,UAClC,WAAa,EAAAE,0CAAA;AAAA,UACb,YAAA,EAAc,OAAO,YAA2B,KAAA;AAC9C,YAAO,OAAA,uBAAA,CAAwB,YAAY,YAAY,CAAA;AAAA,WACzD;AAAA,UACA,WAAa,EAAAC,mDAAA;AAAA,UACb,KAAA,EAAO,MAAO,CAAA,MAAA,CAAOC,WAAc;AAAA,SACpC,CAAA;AAGD,QAAA,MAAMC,kBAAQ,QAAQ,CAAA;AAEtB,QAAM,MAAA,MAAA,GAAS,MAAM,QAAA,CAAS,SAAU,EAAA;AACxC,QAAM,MAAA,cAAA,GAAiB,IAAIC,yCAAA,CAAqB,MAAM,CAAA;AAEtD,QAAM,MAAA,oBAAA,GAAuB,IAAIC,yCAAqB,CAAA;AAAA,UACpD,OAAA;AAAA,UACA,IAAA;AAAA,UACA,QAAU,EAAA,uBAAA;AAAA,UACV,QAAU,EAAA;AAAA,SACX,CAAA;AAED,QAAAC,eAAA,CAAU,MAAO,CAAA;AAAA,UACf,IAAA;AAAA,UACA,OAAA;AAAA,UACA,MAAA;AAAA,UACA,MAAA;AAAA,UACA,SAAA;AAAA,UACA,QAAU,EAAA,cAAA;AAAA,UACV,uBAAA;AAAA,UACA,kBAAA,EAAoB,IAAIC,qCAAmB;AAAA,SAC5C,EAAE,KAAM,EAAA;AAET,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,mBAAa,CAAA;AAAA,YACjB,uBAAA;AAAA,YACA,oBAAA;AAAA,YACA,OAAA;AAAA,YACA,QAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
|
|
@@ -8,10 +8,16 @@ class MetricProvidersRegistry {
|
|
|
8
8
|
register(metricProvider) {
|
|
9
9
|
const providerId = metricProvider.getProviderId();
|
|
10
10
|
const providerDatasource = metricProvider.getProviderDatasourceId();
|
|
11
|
-
const
|
|
12
|
-
|
|
11
|
+
const metric = metricProvider.getMetric();
|
|
12
|
+
const metricType = metricProvider.getMetricType();
|
|
13
|
+
if (providerId !== metric.id) {
|
|
13
14
|
throw new Error(
|
|
14
|
-
`Invalid metric provider with ID ${providerId}, provider ID must match metric ID '${
|
|
15
|
+
`Invalid metric provider with ID ${providerId}, provider ID must match metric ID '${metric.id}'`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
if (metricType !== metric.type) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Invalid metric provider with ID ${providerId}, getMetricType() must match getMetric().type. Expected '${metricType}', but got '${metric.type}'`
|
|
15
21
|
);
|
|
16
22
|
}
|
|
17
23
|
const expectedPrefix = `${providerDatasource}.`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricProvidersRegistry.cjs.js","sources":["../../src/providers/MetricProvidersRegistry.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 { ConflictError, NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n MetricValue,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Entity } from '@backstage/catalog-model';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\n\n/**\n * Registry of all registered metric providers.\n */\nexport class MetricProvidersRegistry {\n private readonly metricProviders = new Map<string, MetricProvider>();\n private readonly datasourceIndex = new Map<string, Set<string>>();\n\n register(metricProvider: MetricProvider): void {\n const providerId = metricProvider.getProviderId();\n const providerDatasource = metricProvider.getProviderDatasourceId();\n const
|
|
1
|
+
{"version":3,"file":"MetricProvidersRegistry.cjs.js","sources":["../../src/providers/MetricProvidersRegistry.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 { ConflictError, NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n MetricValue,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Entity } from '@backstage/catalog-model';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\n\n/**\n * Registry of all registered metric providers.\n */\nexport class MetricProvidersRegistry {\n private readonly metricProviders = new Map<string, MetricProvider>();\n private readonly datasourceIndex = new Map<string, Set<string>>();\n\n register(metricProvider: MetricProvider): void {\n const providerId = metricProvider.getProviderId();\n const providerDatasource = metricProvider.getProviderDatasourceId();\n const metric = metricProvider.getMetric();\n const metricType = metricProvider.getMetricType();\n\n if (providerId !== metric.id) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, provider ID must match metric ID '${metric.id}'`,\n );\n }\n\n if (metricType !== metric.type) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, getMetricType() must match getMetric().type. Expected '${metricType}', but got '${metric.type}'`,\n );\n }\n\n const expectedPrefix = `${providerDatasource}.`;\n if (\n !providerId.startsWith(expectedPrefix) ||\n providerId === expectedPrefix\n ) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, must have format '${providerDatasource}.<metric_name>' where metric name is not empty`,\n );\n }\n\n if (this.metricProviders.has(providerId)) {\n throw new ConflictError(\n `Metric provider with ID '${providerId}' has already been registered`,\n );\n }\n\n this.metricProviders.set(providerId, metricProvider);\n\n let datasourceProviders = this.datasourceIndex.get(providerDatasource);\n if (!datasourceProviders) {\n datasourceProviders = new Set();\n this.datasourceIndex.set(providerDatasource, datasourceProviders);\n }\n datasourceProviders.add(providerId);\n }\n\n getProvider(providerId: string): MetricProvider {\n const metricProvider = this.metricProviders.get(providerId);\n if (!metricProvider) {\n throw new NotFoundError(\n `Metric provider with ID '${providerId}' is not registered.`,\n );\n }\n return metricProvider;\n }\n\n getMetric(providerId: string): Metric {\n return this.getProvider(providerId).getMetric();\n }\n\n async calculateMetric(\n providerId: string,\n entity: Entity,\n ): Promise<MetricValue> {\n return this.getProvider(providerId).calculateMetric(entity);\n }\n\n async calculateMetrics(\n providerIds: string[],\n entity: Entity,\n ): Promise<{ providerId: string; value?: MetricValue; error?: Error }[]> {\n const results = await Promise.allSettled(\n providerIds.map(providerId => this.calculateMetric(providerId, entity)),\n );\n\n return results.map((result, index) => {\n const providerId = providerIds[index];\n if (result.status === 'fulfilled') {\n return { providerId, value: result.value };\n }\n return { providerId, error: result.reason as Error };\n });\n }\n\n listProviders(): MetricProvider[] {\n return Array.from(this.metricProviders.values());\n }\n\n listMetrics(providerIds?: string[]): Metric[] {\n if (providerIds && providerIds.length !== 0) {\n return providerIds\n .map(providerId => this.metricProviders.get(providerId)?.getMetric())\n .filter((m): m is Metric => m !== undefined);\n }\n return [...this.metricProviders.values()].map(provider =>\n provider.getMetric(),\n );\n }\n\n listMetricsByDatasource(datasourceId: string): Metric[] {\n const providerIdsOfDatasource = this.datasourceIndex.get(datasourceId);\n\n if (!providerIdsOfDatasource) {\n return [];\n }\n\n return Array.from(providerIdsOfDatasource)\n .map(providerId => this.metricProviders.get(providerId))\n .filter((provider): provider is MetricProvider => provider !== undefined)\n .map(provider => provider.getMetric());\n }\n}\n"],"names":["ConflictError","NotFoundError"],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ class Scheduler {
|
|
|
12
12
|
scheduler;
|
|
13
13
|
database;
|
|
14
14
|
metricProvidersRegistry;
|
|
15
|
+
thresholdEvaluator;
|
|
15
16
|
tasks = [];
|
|
16
17
|
constructor(options) {
|
|
17
18
|
this.auth = options.auth;
|
|
@@ -21,6 +22,7 @@ class Scheduler {
|
|
|
21
22
|
this.scheduler = options.scheduler;
|
|
22
23
|
this.database = options.database;
|
|
23
24
|
this.metricProvidersRegistry = options.metricProvidersRegistry;
|
|
25
|
+
this.thresholdEvaluator = options.thresholdEvaluator;
|
|
24
26
|
}
|
|
25
27
|
static create(options) {
|
|
26
28
|
return new Scheduler(options);
|
|
@@ -68,7 +70,8 @@ class Scheduler {
|
|
|
68
70
|
database: this.database,
|
|
69
71
|
config: this.config,
|
|
70
72
|
catalog: this.catalog,
|
|
71
|
-
auth: this.auth
|
|
73
|
+
auth: this.auth,
|
|
74
|
+
thresholdEvaluator: this.thresholdEvaluator
|
|
72
75
|
},
|
|
73
76
|
provider
|
|
74
77
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../../src/scheduler/index.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 AuthService,\n LoggerService,\n SchedulerService,\n} from '@backstage/backend-plugin-api';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport type { Config } from '@backstage/config';\nimport { CLEANUP_EXPIRED_METRICS_ID } from './constants';\nimport { CleanupExpiredMetricsTask } from './tasks/CleanupExpiredMetricsTask';\nimport { PullMetricsByProviderTask } from './tasks/PullMetricsByProviderTask';\nimport { SchedulerOptions, SchedulerTask } from './types';\nimport { DatabaseMetricValues } from '../database/DatabaseMetricValues';\n\nexport class Scheduler {\n private readonly auth: AuthService;\n private readonly catalog: CatalogService;\n private readonly config: Config;\n private readonly logger: LoggerService;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly metricProvidersRegistry: MetricProvidersRegistry;\n\n private tasks: Array<{ name: string; task: SchedulerTask }> = [];\n\n private constructor(options: SchedulerOptions) {\n this.auth = options.auth;\n this.catalog = options.catalog;\n this.config = options.config;\n this.logger = options.logger;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.metricProvidersRegistry = options.metricProvidersRegistry;\n }\n\n static create(options: SchedulerOptions): Scheduler {\n return new Scheduler(options);\n }\n\n async start(): Promise<void> {\n this.initializeTasks();\n this.initializeTasksByProviders();\n\n const results = await Promise.allSettled(\n this.tasks.map(({ name, task }) => this.startTask(name, task)),\n );\n\n const successCount = results.filter(r => r.status === 'fulfilled').length;\n\n let index = 0;\n\n for (const result of results) {\n if (result.status === 'rejected') {\n this.logger.warn(\n `Failed to start task '${this.tasks[index].name}': ${result.reason}`,\n );\n }\n index++;\n }\n\n this.logger.info(`Scheduled: ${successCount}/${this.tasks.length} tasks`);\n }\n\n private initializeTasks(): void {\n this.tasks = [\n {\n name: CLEANUP_EXPIRED_METRICS_ID,\n task: new CleanupExpiredMetricsTask({\n scheduler: this.scheduler,\n logger: this.logger,\n database: this.database,\n config: this.config,\n }),\n },\n ];\n }\n\n private initializeTasksByProviders(): void {\n const providers = this.metricProvidersRegistry.listProviders();\n\n for (const provider of providers) {\n this.tasks.push({\n name: provider.getProviderId(),\n task: new PullMetricsByProviderTask(\n {\n scheduler: this.scheduler,\n logger: this.logger,\n database: this.database,\n config: this.config,\n catalog: this.catalog,\n auth: this.auth,\n },\n provider,\n ),\n });\n }\n }\n\n private async startTask(name: string, task: SchedulerTask): Promise<void> {\n try {\n await task.start();\n } catch (error) {\n this.logger.error(`Failed to start task '${name}': ${error}`, error);\n throw error;\n }\n }\n}\n"],"names":["CLEANUP_EXPIRED_METRICS_ID","CleanupExpiredMetricsTask","PullMetricsByProviderTask"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../../src/scheduler/index.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 AuthService,\n LoggerService,\n SchedulerService,\n} from '@backstage/backend-plugin-api';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport type { Config } from '@backstage/config';\nimport { CLEANUP_EXPIRED_METRICS_ID } from './constants';\nimport { CleanupExpiredMetricsTask } from './tasks/CleanupExpiredMetricsTask';\nimport { PullMetricsByProviderTask } from './tasks/PullMetricsByProviderTask';\nimport { SchedulerOptions, SchedulerTask } from './types';\nimport { DatabaseMetricValues } from '../database/DatabaseMetricValues';\nimport { ThresholdEvaluator } from '../threshold/ThresholdEvaluator';\n\nexport class Scheduler {\n private readonly auth: AuthService;\n private readonly catalog: CatalogService;\n private readonly config: Config;\n private readonly logger: LoggerService;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly metricProvidersRegistry: MetricProvidersRegistry;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n\n private tasks: Array<{ name: string; task: SchedulerTask }> = [];\n\n private constructor(options: SchedulerOptions) {\n this.auth = options.auth;\n this.catalog = options.catalog;\n this.config = options.config;\n this.logger = options.logger;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.metricProvidersRegistry = options.metricProvidersRegistry;\n this.thresholdEvaluator = options.thresholdEvaluator;\n }\n\n static create(options: SchedulerOptions): Scheduler {\n return new Scheduler(options);\n }\n\n async start(): Promise<void> {\n this.initializeTasks();\n this.initializeTasksByProviders();\n\n const results = await Promise.allSettled(\n this.tasks.map(({ name, task }) => this.startTask(name, task)),\n );\n\n const successCount = results.filter(r => r.status === 'fulfilled').length;\n\n let index = 0;\n\n for (const result of results) {\n if (result.status === 'rejected') {\n this.logger.warn(\n `Failed to start task '${this.tasks[index].name}': ${result.reason}`,\n );\n }\n index++;\n }\n\n this.logger.info(`Scheduled: ${successCount}/${this.tasks.length} tasks`);\n }\n\n private initializeTasks(): void {\n this.tasks = [\n {\n name: CLEANUP_EXPIRED_METRICS_ID,\n task: new CleanupExpiredMetricsTask({\n scheduler: this.scheduler,\n logger: this.logger,\n database: this.database,\n config: this.config,\n }),\n },\n ];\n }\n\n private initializeTasksByProviders(): void {\n const providers = this.metricProvidersRegistry.listProviders();\n\n for (const provider of providers) {\n this.tasks.push({\n name: provider.getProviderId(),\n task: new PullMetricsByProviderTask(\n {\n scheduler: this.scheduler,\n logger: this.logger,\n database: this.database,\n config: this.config,\n catalog: this.catalog,\n auth: this.auth,\n thresholdEvaluator: this.thresholdEvaluator,\n },\n provider,\n ),\n });\n }\n }\n\n private async startTask(name: string, task: SchedulerTask): Promise<void> {\n try {\n await task.start();\n } catch (error) {\n this.logger.error(`Failed to start task '${name}': ${error}`, error);\n throw error;\n }\n }\n}\n"],"names":["CLEANUP_EXPIRED_METRICS_ID","CleanupExpiredMetricsTask","PullMetricsByProviderTask"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var mergeEntityAndProviderThresholds = require('../../utils/mergeEntityAndProviderThresholds.cjs.js');
|
|
4
5
|
var uuid = require('uuid');
|
|
5
6
|
var catalogModel = require('@backstage/catalog-model');
|
|
6
7
|
|
|
@@ -13,6 +14,7 @@ class PullMetricsByProviderTask {
|
|
|
13
14
|
provider;
|
|
14
15
|
scheduler;
|
|
15
16
|
database;
|
|
17
|
+
thresholdEvaluator;
|
|
16
18
|
static CATALOG_BATCH_SIZE = 50;
|
|
17
19
|
static DEFAULT_SCHEDULE = {
|
|
18
20
|
frequency: { hours: 1 },
|
|
@@ -28,6 +30,7 @@ class PullMetricsByProviderTask {
|
|
|
28
30
|
this.provider = provider;
|
|
29
31
|
this.scheduler = options.scheduler;
|
|
30
32
|
this.database = options.database;
|
|
33
|
+
this.thresholdEvaluator = options.thresholdEvaluator;
|
|
31
34
|
}
|
|
32
35
|
async start() {
|
|
33
36
|
const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;
|
|
@@ -61,6 +64,7 @@ class PullMetricsByProviderTask {
|
|
|
61
64
|
logger.info(`Pulling metrics for ${this.providerId}`);
|
|
62
65
|
let totalProcessed = 0;
|
|
63
66
|
let cursor = void 0;
|
|
67
|
+
const metricType = provider.getMetricType();
|
|
64
68
|
try {
|
|
65
69
|
do {
|
|
66
70
|
const entitiesResponse = await this.catalog.queryEntities(
|
|
@@ -74,20 +78,30 @@ class PullMetricsByProviderTask {
|
|
|
74
78
|
cursor = entitiesResponse.pageInfo.nextCursor;
|
|
75
79
|
const batchResults = await Promise.allSettled(
|
|
76
80
|
entitiesResponse.items.map(async (entity) => {
|
|
81
|
+
let value;
|
|
77
82
|
try {
|
|
78
|
-
|
|
83
|
+
value = await provider.calculateMetric(entity);
|
|
84
|
+
const thresholds = mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(
|
|
85
|
+
entity,
|
|
86
|
+
provider
|
|
87
|
+
);
|
|
88
|
+
const status = this.thresholdEvaluator.getFirstMatchingThreshold(
|
|
89
|
+
value,
|
|
90
|
+
metricType,
|
|
91
|
+
thresholds
|
|
92
|
+
);
|
|
79
93
|
return {
|
|
80
94
|
catalog_entity_ref: catalogModel.stringifyEntityRef(entity),
|
|
81
95
|
metric_id: this.providerId,
|
|
82
96
|
value,
|
|
83
97
|
timestamp: /* @__PURE__ */ new Date(),
|
|
84
|
-
|
|
98
|
+
status
|
|
85
99
|
};
|
|
86
100
|
} catch (error) {
|
|
87
101
|
return {
|
|
88
102
|
catalog_entity_ref: catalogModel.stringifyEntityRef(entity),
|
|
89
103
|
metric_id: this.providerId,
|
|
90
|
-
value
|
|
104
|
+
value,
|
|
91
105
|
timestamp: /* @__PURE__ */ new Date(),
|
|
92
106
|
error_message: error instanceof Error ? error.message : String(error)
|
|
93
107
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PullMetricsByProviderTask.cjs.js","sources":["../../../src/scheduler/tasks/PullMetricsByProviderTask.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport {\n AuthService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { v4 as uuid } from 'uuid';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DbMetricValue } from '../../database/types';\nimport { SchedulerOptions, SchedulerTask } from '../types';\n\ntype Options = Pick<\n SchedulerOptions,\n 'scheduler' | 'logger' | 'database' | 'config' | 'catalog' | 'auth'\n>;\n\nexport class PullMetricsByProviderTask implements SchedulerTask {\n private readonly config: Config;\n private readonly auth: AuthService;\n private readonly providerId: string;\n private readonly logger: LoggerService;\n private readonly catalog: CatalogService;\n private readonly provider: MetricProvider;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n\n private static readonly CATALOG_BATCH_SIZE = 50;\n\n private static readonly DEFAULT_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: { hours: 1 },\n timeout: { minutes: 15 },\n initialDelay: { seconds: 3 },\n };\n\n constructor(options: Options, provider: MetricProvider) {\n this.config = options.config;\n this.auth = options.auth;\n this.providerId = provider.getProviderId();\n this.logger = options.logger;\n this.catalog = options.catalog;\n this.provider = provider;\n this.scheduler = options.scheduler;\n this.database = options.database;\n }\n\n async start(): Promise<void> {\n const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;\n const schedule = this.getScheduleFromConfig(scheduleConfigPath);\n\n const taskRunner = this.scheduler.createScheduledTaskRunner(schedule);\n\n await taskRunner.run({\n id: this.providerId,\n fn: async () => {\n const logger = this.logger.child({\n class: this.constructor.name,\n taskId: this.providerId,\n taskInstanceId: uuid(),\n });\n\n try {\n await this.pullProviderMetrics(this.provider, logger);\n } catch (error) {\n logger.error(\n `${this.providerId} pulling metrics failed, ${error}`,\n error,\n );\n }\n },\n });\n }\n\n private getScheduleFromConfig(\n schedulePath: string,\n ): SchedulerServiceTaskScheduleDefinition {\n return this.config.has(schedulePath)\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n this.config.getConfig(schedulePath),\n )\n : PullMetricsByProviderTask.DEFAULT_SCHEDULE;\n }\n\n private async pullProviderMetrics(\n provider: MetricProvider,\n logger: LoggerService,\n ): Promise<void> {\n logger.info(`Pulling metrics for ${this.providerId}`);\n\n let totalProcessed = 0;\n let cursor: string | undefined = undefined;\n\n try {\n do {\n const entitiesResponse = await this.catalog.queryEntities(\n {\n filter: provider.getCatalogFilter(),\n limit: PullMetricsByProviderTask.CATALOG_BATCH_SIZE,\n ...(cursor ? { cursor } : {}),\n },\n { credentials: await this.auth.getOwnServiceCredentials() },\n );\n\n cursor = entitiesResponse.pageInfo.nextCursor;\n\n const batchResults = await Promise.allSettled(\n entitiesResponse.items.map(async entity => {\n try {\n const value = await provider.calculateMetric(entity);\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n error_message: undefined,\n };\n } catch (error) {\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value: undefined,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n };\n }\n }),\n ).then(promises =>\n promises.reduce((acc, curr) => {\n if (curr.status === 'fulfilled') {\n return [...acc, curr.value];\n }\n return acc;\n }, [] as Omit<DbMetricValue, 'id'>[]),\n );\n\n await this.database.createMetricValues(batchResults);\n totalProcessed += entitiesResponse.items.length;\n } while (cursor !== undefined);\n\n logger.info(\n `Completed metric pull for ${this.providerId}: processed ${totalProcessed} entities`,\n );\n } catch (error) {\n logger.error(`Failed to pull metrics for ${this.providerId}: ${error}`);\n\n throw error;\n }\n }\n}\n"],"names":["uuid","readSchedulerServiceTaskScheduleDefinitionFromConfig","stringifyEntityRef"],"mappings":";;;;;;AAqCO,MAAM,yBAAmD,CAAA;AAAA,EAC7C,MAAA;AAAA,EACA,IAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EAEjB,OAAwB,kBAAqB,GAAA,EAAA;AAAA,EAE7C,OAAwB,gBACtB,GAAA;AAAA,IACE,SAAA,EAAW,EAAE,KAAA,EAAO,CAAE,EAAA;AAAA,IACtB,OAAA,EAAS,EAAE,OAAA,EAAS,EAAG,EAAA;AAAA,IACvB,YAAA,EAAc,EAAE,OAAA,EAAS,CAAE;AAAA,GAC7B;AAAA,EAEF,WAAA,CAAY,SAAkB,QAA0B,EAAA;AACtD,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,OAAO,OAAQ,CAAA,IAAA;AACpB,IAAK,IAAA,CAAA,UAAA,GAAa,SAAS,aAAc,EAAA;AACzC,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,UAAU,OAAQ,CAAA,OAAA;AACvB,IAAA,IAAA,CAAK,QAAW,GAAA,QAAA;AAChB,IAAA,IAAA,CAAK,YAAY,OAAQ,CAAA,SAAA;AACzB,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AAAA;AAC1B,EAEA,MAAM,KAAuB,GAAA;AAC3B,IAAM,MAAA,kBAAA,GAAqB,CAAqB,kBAAA,EAAA,IAAA,CAAK,UAAU,CAAA,SAAA,CAAA;AAC/D,IAAM,MAAA,QAAA,GAAW,IAAK,CAAA,qBAAA,CAAsB,kBAAkB,CAAA;AAE9D,IAAA,MAAM,UAAa,GAAA,IAAA,CAAK,SAAU,CAAA,yBAAA,CAA0B,QAAQ,CAAA;AAEpE,IAAA,MAAM,WAAW,GAAI,CAAA;AAAA,MACnB,IAAI,IAAK,CAAA,UAAA;AAAA,MACT,IAAI,YAAY;AACd,QAAM,MAAA,MAAA,GAAS,IAAK,CAAA,MAAA,CAAO,KAAM,CAAA;AAAA,UAC/B,KAAA,EAAO,KAAK,WAAY,CAAA,IAAA;AAAA,UACxB,QAAQ,IAAK,CAAA,UAAA;AAAA,UACb,gBAAgBA,OAAK;AAAA,SACtB,CAAA;AAED,QAAI,IAAA;AACF,UAAA,MAAM,IAAK,CAAA,mBAAA,CAAoB,IAAK,CAAA,QAAA,EAAU,MAAM,CAAA;AAAA,iBAC7C,KAAO,EAAA;AACd,UAAO,MAAA,CAAA,KAAA;AAAA,YACL,CAAG,EAAA,IAAA,CAAK,UAAU,CAAA,yBAAA,EAA4B,KAAK,CAAA,CAAA;AAAA,YACnD;AAAA,WACF;AAAA;AACF;AACF,KACD,CAAA;AAAA;AACH,EAEQ,sBACN,YACwC,EAAA;AACxC,IAAA,OAAO,IAAK,CAAA,MAAA,CAAO,GAAI,CAAA,YAAY,CAC/B,GAAAC,qEAAA;AAAA,MACE,IAAA,CAAK,MAAO,CAAA,SAAA,CAAU,YAAY;AAAA,QAEpC,yBAA0B,CAAA,gBAAA;AAAA;AAChC,EAEA,MAAc,mBACZ,CAAA,QAAA,EACA,MACe,EAAA;AACf,IAAA,MAAA,CAAO,IAAK,CAAA,CAAA,oBAAA,EAAuB,IAAK,CAAA,UAAU,CAAE,CAAA,CAAA;AAEpD,IAAA,IAAI,cAAiB,GAAA,CAAA;AACrB,IAAA,IAAI,MAA6B,GAAA,MAAA;AAEjC,IAAI,IAAA;AACF,MAAG,GAAA;AACD,QAAM,MAAA,gBAAA,GAAmB,MAAM,IAAA,CAAK,OAAQ,CAAA,aAAA;AAAA,UAC1C;AAAA,YACE,MAAA,EAAQ,SAAS,gBAAiB,EAAA;AAAA,YAClC,OAAO,yBAA0B,CAAA,kBAAA;AAAA,YACjC,GAAI,MAAA,GAAS,EAAE,MAAA,KAAW;AAAC,WAC7B;AAAA,UACA,EAAE,WAAa,EAAA,MAAM,IAAK,CAAA,IAAA,CAAK,0BAA2B;AAAA,SAC5D;AAEA,QAAA,MAAA,GAAS,iBAAiB,QAAS,CAAA,UAAA;AAEnC,QAAM,MAAA,YAAA,GAAe,MAAM,OAAQ,CAAA,UAAA;AAAA,UACjC,gBAAiB,CAAA,KAAA,CAAM,GAAI,CAAA,OAAM,MAAU,KAAA;AACzC,YAAI,IAAA;AACF,cAAA,MAAM,KAAQ,GAAA,MAAM,QAAS,CAAA,eAAA,CAAgB,MAAM,CAAA;AACnD,cAAO,OAAA;AAAA,gBACL,kBAAA,EAAoBC,gCAAmB,MAAM,CAAA;AAAA,gBAC7C,WAAW,IAAK,CAAA,UAAA;AAAA,gBAChB,KAAA;AAAA,gBACA,SAAA,sBAAe,IAAK,EAAA;AAAA,gBACpB,aAAe,EAAA,KAAA;AAAA,eACjB;AAAA,qBACO,KAAO,EAAA;AACd,cAAO,OAAA;AAAA,gBACL,kBAAA,EAAoBA,gCAAmB,MAAM,CAAA;AAAA,gBAC7C,WAAW,IAAK,CAAA,UAAA;AAAA,gBAChB,KAAO,EAAA,KAAA,CAAA;AAAA,gBACP,SAAA,sBAAe,IAAK,EAAA;AAAA,gBACpB,eACE,KAAiB,YAAA,KAAA,GAAQ,KAAM,CAAA,OAAA,GAAU,OAAO,KAAK;AAAA,eACzD;AAAA;AACF,WACD;AAAA,SACD,CAAA,IAAA;AAAA,UAAK,CACL,QAAA,KAAA,QAAA,CAAS,MAAO,CAAA,CAAC,KAAK,IAAS,KAAA;AAC7B,YAAI,IAAA,IAAA,CAAK,WAAW,WAAa,EAAA;AAC/B,cAAA,OAAO,CAAC,GAAG,GAAK,EAAA,IAAA,CAAK,KAAK,CAAA;AAAA;AAE5B,YAAO,OAAA,GAAA;AAAA,WACT,EAAG,EAAiC;AAAA,SACtC;AAEA,QAAM,MAAA,IAAA,CAAK,QAAS,CAAA,kBAAA,CAAmB,YAAY,CAAA;AACnD,QAAA,cAAA,IAAkB,iBAAiB,KAAM,CAAA,MAAA;AAAA,eAClC,MAAW,KAAA,KAAA,CAAA;AAEpB,MAAO,MAAA,CAAA,IAAA;AAAA,QACL,CAA6B,0BAAA,EAAA,IAAA,CAAK,UAAU,CAAA,YAAA,EAAe,cAAc,CAAA,SAAA;AAAA,OAC3E;AAAA,aACO,KAAO,EAAA;AACd,MAAA,MAAA,CAAO,MAAM,CAA8B,2BAAA,EAAA,IAAA,CAAK,UAAU,CAAA,EAAA,EAAK,KAAK,CAAE,CAAA,CAAA;AAEtE,MAAM,MAAA,KAAA;AAAA;AACR;AAEJ;;;;"}
|
|
1
|
+
{"version":3,"file":"PullMetricsByProviderTask.cjs.js","sources":["../../../src/scheduler/tasks/PullMetricsByProviderTask.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport {\n AuthService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { mergeEntityAndProviderThresholds } from '../../utils/mergeEntityAndProviderThresholds';\nimport { v4 as uuid } from 'uuid';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DbMetricValueCreate } from '../../database/types';\nimport { SchedulerOptions, SchedulerTask } from '../types';\nimport { ThresholdEvaluator } from '../../threshold/ThresholdEvaluator';\nimport { MetricValue } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\ntype Options = Pick<\n SchedulerOptions,\n | 'scheduler'\n | 'logger'\n | 'database'\n | 'config'\n | 'catalog'\n | 'auth'\n | 'thresholdEvaluator'\n>;\n\nexport class PullMetricsByProviderTask implements SchedulerTask {\n private readonly config: Config;\n private readonly auth: AuthService;\n private readonly providerId: string;\n private readonly logger: LoggerService;\n private readonly catalog: CatalogService;\n private readonly provider: MetricProvider;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n\n private static readonly CATALOG_BATCH_SIZE = 50;\n\n private static readonly DEFAULT_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: { hours: 1 },\n timeout: { minutes: 15 },\n initialDelay: { seconds: 3 },\n };\n\n constructor(options: Options, provider: MetricProvider) {\n this.config = options.config;\n this.auth = options.auth;\n this.providerId = provider.getProviderId();\n this.logger = options.logger;\n this.catalog = options.catalog;\n this.provider = provider;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.thresholdEvaluator = options.thresholdEvaluator;\n }\n\n async start(): Promise<void> {\n const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;\n const schedule = this.getScheduleFromConfig(scheduleConfigPath);\n\n const taskRunner = this.scheduler.createScheduledTaskRunner(schedule);\n\n await taskRunner.run({\n id: this.providerId,\n fn: async () => {\n const logger = this.logger.child({\n class: this.constructor.name,\n taskId: this.providerId,\n taskInstanceId: uuid(),\n });\n\n try {\n await this.pullProviderMetrics(this.provider, logger);\n } catch (error) {\n logger.error(\n `${this.providerId} pulling metrics failed, ${error}`,\n error,\n );\n }\n },\n });\n }\n\n private getScheduleFromConfig(\n schedulePath: string,\n ): SchedulerServiceTaskScheduleDefinition {\n return this.config.has(schedulePath)\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n this.config.getConfig(schedulePath),\n )\n : PullMetricsByProviderTask.DEFAULT_SCHEDULE;\n }\n\n private async pullProviderMetrics(\n provider: MetricProvider,\n logger: LoggerService,\n ): Promise<void> {\n logger.info(`Pulling metrics for ${this.providerId}`);\n\n let totalProcessed = 0;\n let cursor: string | undefined = undefined;\n\n const metricType = provider.getMetricType();\n\n try {\n do {\n const entitiesResponse = await this.catalog.queryEntities(\n {\n filter: provider.getCatalogFilter(),\n limit: PullMetricsByProviderTask.CATALOG_BATCH_SIZE,\n ...(cursor ? { cursor } : {}),\n },\n { credentials: await this.auth.getOwnServiceCredentials() },\n );\n\n cursor = entitiesResponse.pageInfo.nextCursor;\n\n const batchResults = await Promise.allSettled(\n entitiesResponse.items.map(async entity => {\n let value: MetricValue | undefined;\n\n try {\n value = await provider.calculateMetric(entity);\n\n const thresholds = mergeEntityAndProviderThresholds(\n entity,\n provider,\n );\n\n const status = this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metricType,\n thresholds,\n );\n\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n status,\n } as DbMetricValueCreate;\n } catch (error) {\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n } as DbMetricValueCreate;\n }\n }),\n ).then(promises =>\n promises.reduce((acc, curr) => {\n if (curr.status === 'fulfilled') {\n return [...acc, curr.value];\n }\n return acc;\n }, [] as DbMetricValueCreate[]),\n );\n\n await this.database.createMetricValues(batchResults);\n totalProcessed += entitiesResponse.items.length;\n } while (cursor !== undefined);\n\n logger.info(\n `Completed metric pull for ${this.providerId}: processed ${totalProcessed} entities`,\n );\n } catch (error) {\n logger.error(`Failed to pull metrics for ${this.providerId}: ${error}`);\n\n throw error;\n }\n }\n}\n"],"names":["uuid","readSchedulerServiceTaskScheduleDefinitionFromConfig","mergeEntityAndProviderThresholds","stringifyEntityRef"],"mappings":"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"}
|