@red-hat-developer-hub/backstage-plugin-catalog-backend-module-model-catalog 1.0.1 → 1.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
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-catalog-backend-module-model-catalog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e15e722: Add support for the `rhdh.io/api-entity-ref` annotation on KServe InferenceServices. The annotation value is normalized to a fully qualified entity reference and set as `spec.apiEntityRef` on the generated `AiModelServerAPI` entity.
|
|
8
|
+
|
|
9
|
+
## 1.0.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- dbce029: Bump ai-integrations workspace to Backstage v1.54.0
|
|
14
|
+
- Updated dependencies [dbce029]
|
|
15
|
+
- @red-hat-developer-hub/backstage-plugin-catalog-model-ai-model-server@0.2.1
|
|
16
|
+
|
|
3
17
|
## 1.0.1
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
var catalogModel = require('@backstage/catalog-model');
|
|
4
4
|
|
|
5
5
|
const TECHDOCS_KEY = "techdocs";
|
|
6
|
+
const SYSTEM_ANNOTATION = "rhdh.io/system";
|
|
7
|
+
const SERVER_TYPE_ANNOTATION = "rhdh.io/serverType";
|
|
8
|
+
const DEFAULT_ANNOTATION = "rhdh.io/default";
|
|
9
|
+
const OWNER_ANNOTATION = "rhdh.io/owner";
|
|
10
|
+
const LIFECYCLE_ANNOTATION = "rhdh.io/lifecycle";
|
|
11
|
+
const API_ENTITY_REF_ANNOTATION = "rhdh.io/api-entity-ref";
|
|
6
12
|
function GenerateCatalogEntities(modelCatalog, svcUrl, logger) {
|
|
7
13
|
if (!modelCatalog.modelServer) {
|
|
8
14
|
return [];
|
|
@@ -32,6 +38,12 @@ function GenerateCatalogEntities(modelCatalog, svcUrl, logger) {
|
|
|
32
38
|
if (modelServer.annotations) {
|
|
33
39
|
Object.assign(annotations, modelServer.annotations);
|
|
34
40
|
}
|
|
41
|
+
delete annotations[SYSTEM_ANNOTATION];
|
|
42
|
+
delete annotations[SERVER_TYPE_ANNOTATION];
|
|
43
|
+
delete annotations[DEFAULT_ANNOTATION];
|
|
44
|
+
delete annotations[OWNER_ANNOTATION];
|
|
45
|
+
delete annotations[LIFECYCLE_ANNOTATION];
|
|
46
|
+
delete annotations[API_ENTITY_REF_ANNOTATION];
|
|
35
47
|
for (const model of models) {
|
|
36
48
|
if (model.annotations?.[TECHDOCS_KEY]) {
|
|
37
49
|
let techdocsUrl = model.annotations[TECHDOCS_KEY].trim();
|
|
@@ -52,6 +64,13 @@ function GenerateCatalogEntities(modelCatalog, svcUrl, logger) {
|
|
|
52
64
|
});
|
|
53
65
|
}
|
|
54
66
|
}
|
|
67
|
+
const systemOverride = modelServer.annotations?.[SYSTEM_ANNOTATION];
|
|
68
|
+
const serverTypeOverride = modelServer.annotations?.[SERVER_TYPE_ANNOTATION];
|
|
69
|
+
const defaultOverride = modelServer.annotations?.[DEFAULT_ANNOTATION];
|
|
70
|
+
const ownerOverride = modelServer.annotations?.[OWNER_ANNOTATION];
|
|
71
|
+
const lifecycleOverride = modelServer.annotations?.[LIFECYCLE_ANNOTATION];
|
|
72
|
+
const apiEntityRefRaw = modelServer.annotations?.[API_ENTITY_REF_ANNOTATION];
|
|
73
|
+
const apiEntityRefNormalized = apiEntityRefRaw ? normalizeApiEntityRef(apiEntityRefRaw) : void 0;
|
|
55
74
|
const entity = {
|
|
56
75
|
apiVersion: "backstage.io/v1alpha1",
|
|
57
76
|
kind: "AiModelServerAPI",
|
|
@@ -64,24 +83,48 @@ function GenerateCatalogEntities(modelCatalog, svcUrl, logger) {
|
|
|
64
83
|
},
|
|
65
84
|
spec: {
|
|
66
85
|
type: "ai-model-server",
|
|
67
|
-
lifecycle: modelServer.lifecycle,
|
|
68
|
-
owner: `user:${modelServer.owner}`,
|
|
69
|
-
|
|
86
|
+
lifecycle: lifecycleOverride ?? modelServer.lifecycle,
|
|
87
|
+
owner: `user:${ownerOverride ?? modelServer.owner}`,
|
|
88
|
+
...systemOverride && { system: systemOverride },
|
|
89
|
+
...apiEntityRefNormalized && {
|
|
90
|
+
apiEntityRef: apiEntityRefNormalized
|
|
91
|
+
},
|
|
92
|
+
serverType: serverTypeOverride ?? modelServer.API?.type ?? "unknown",
|
|
70
93
|
serverUrl: modelServer.API?.url ?? "",
|
|
71
94
|
requiresApiKey: modelServer.authentication ?? false,
|
|
72
95
|
models: {
|
|
73
96
|
available: models.map((m) => sanitizeMetadataName(m.name)),
|
|
74
|
-
...models
|
|
75
|
-
default: sanitizeMetadataName(models[0].name)
|
|
76
|
-
}
|
|
97
|
+
...getDefaultModel(defaultOverride, models)
|
|
77
98
|
}
|
|
78
99
|
}
|
|
79
100
|
};
|
|
80
101
|
return [entity];
|
|
81
102
|
}
|
|
103
|
+
function getDefaultModel(defaultOverride, models) {
|
|
104
|
+
if (defaultOverride) {
|
|
105
|
+
return { default: sanitizeMetadataName(defaultOverride) };
|
|
106
|
+
}
|
|
107
|
+
if (models.length > 0) {
|
|
108
|
+
return { default: sanitizeMetadataName(models[0].name) };
|
|
109
|
+
}
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
82
112
|
function sanitizeMetadataName(modelName) {
|
|
83
113
|
return modelName.replace(/\s/g, "");
|
|
84
114
|
}
|
|
115
|
+
function normalizeApiEntityRef(rawValue) {
|
|
116
|
+
const value = rawValue.trim();
|
|
117
|
+
if (!value) return "";
|
|
118
|
+
if (value.includes(":")) {
|
|
119
|
+
const kind = value.split(":")[0];
|
|
120
|
+
if (kind !== "api") return "";
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
if (value.includes("/")) {
|
|
124
|
+
return `api:${value}`;
|
|
125
|
+
}
|
|
126
|
+
return `api:default/${value}`;
|
|
127
|
+
}
|
|
85
128
|
function sanitizeTags(tags, logger) {
|
|
86
129
|
const sanitizedTags = [];
|
|
87
130
|
tags.forEach((tag) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModelCatalogGenerator.cjs.js","sources":["../../src/clients/ModelCatalogGenerator.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 { ModelCatalog } from '@redhat-ai-dev/model-catalog-types';\nimport { Entity, makeValidator } from '@backstage/catalog-model';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport type { AiModelServerApiEntity } from '@red-hat-developer-hub/backstage-plugin-catalog-model-ai-model-server';\n\nconst TECHDOCS_KEY = 'techdocs';\n\nfunction isModelCatalog(o: any): o is ModelCatalog {\n return 'models' in o || 'modelServer' in o;\n}\n\nexport function ParseCatalogJSON(jsonStr: string): ModelCatalog {\n const modelCatalog: ModelCatalog = JSON.parse(jsonStr);\n if (isModelCatalog(modelCatalog)) {\n return modelCatalog;\n }\n throw new Error(`model catalog JSON in unexpected format: ${jsonStr}`);\n}\n\nexport function GenerateCatalogEntities(\n modelCatalog: ModelCatalog,\n svcUrl?: string,\n logger?: LoggerService,\n): Entity[] {\n if (!modelCatalog.modelServer) {\n logger?.debug(\n 'ModelCatalog has no modelServer; skipping AiModelServerAPI generation',\n );\n return [];\n }\n\n if (!modelCatalog.modelServer.API?.url) {\n logger?.debug(\n 'ModelCatalog modelServer has no API url; skipping AiModelServerAPI generation',\n );\n return [];\n }\n\n const modelServer = modelCatalog.modelServer;\n const models = modelCatalog.models;\n\n const tags = sanitizeTags(modelServer.tags ?? [], logger);\n if (modelServer.authentication === undefined || !modelServer.authentication) {\n tags.push('auth-not-required');\n } else {\n tags.push('auth-required');\n }\n\n const links: Array<{ title: string; url: string }> = [];\n if (modelServer.API) {\n links.push({ title: 'API', url: modelServer.API.url });\n }\n if (modelServer.homepageURL) {\n links.push({ title: 'Homepage', url: modelServer.homepageURL });\n }\n\n const annotations: Record<string, string> = {};\n\n // Copy API annotations\n if (modelServer.API?.annotations) {\n Object.assign(annotations, modelServer.API.annotations);\n }\n // Copy model server annotations (overrides API on conflict)\n if (modelServer.annotations) {\n Object.assign(annotations, modelServer.annotations);\n }\n\n // Collect techdocs from models — use the first one found\n for (const model of models) {\n if (model.annotations?.[TECHDOCS_KEY]) {\n let techdocsUrl = model.annotations[TECHDOCS_KEY].trim();\n if (techdocsUrl !== '') {\n if (svcUrl && techdocsUrl.startsWith('/')) {\n techdocsUrl = svcUrl + techdocsUrl;\n }\n annotations['backstage.io/techdocs-ref'] = `url:${techdocsUrl}`;\n break;\n }\n }\n }\n\n // Collect per-model artifact links\n for (const model of models) {\n if (model.artifactLocationURL) {\n links.push({\n title: `${model.name} artifact`,\n url: model.artifactLocationURL,\n });\n }\n }\n\n const entity: AiModelServerApiEntity = {\n apiVersion: 'backstage.io/v1alpha1',\n kind: 'AiModelServerAPI',\n metadata: {\n name: sanitizeMetadataName(modelServer.name),\n description: modelServer.description,\n tags,\n links,\n ...(Object.keys(annotations).length > 0 && { annotations }),\n },\n spec: {\n type: 'ai-model-server',\n lifecycle: modelServer.lifecycle,\n owner: `user:${modelServer.owner}`,\n serverType: modelServer.API?.type ?? 'unknown',\n serverUrl: modelServer.API?.url ?? '',\n requiresApiKey: modelServer.authentication ?? false,\n models: {\n available: models.map(m => sanitizeMetadataName(m.name)),\n ...(models.length > 0 && {\n default: sanitizeMetadataName(models[0].name),\n }),\n },\n },\n };\n\n return [entity];\n}\n\nfunction sanitizeMetadataName(modelName: string): string {\n return modelName.replace(/\\s/g, '');\n}\n\nfunction sanitizeTags(tags: string[], logger?: LoggerService): string[] {\n const sanitizedTags: string[] = [];\n tags.forEach(tag => {\n let sanitizedTag: string = tag;\n sanitizedTag = sanitizedTag.replace(/\\s/g, '');\n\n if (!makeValidator().isValidTag(sanitizedTag)) {\n if (logger !== undefined) {\n logger.error(\n `invalid tag: ${sanitizedTag}. Tags are expected to be less than 63 characters and conform to: ^[a-z0-9:+#]+(\\-[a-z0-9:+#]+)*$`,\n );\n }\n return;\n }\n sanitizedTags.push(sanitizedTag);\n });\n return sanitizedTags;\n}\n"],"names":["makeValidator"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ModelCatalogGenerator.cjs.js","sources":["../../src/clients/ModelCatalogGenerator.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 { ModelCatalog } from '@redhat-ai-dev/model-catalog-types';\nimport { Entity, makeValidator } from '@backstage/catalog-model';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport type { AiModelServerApiEntity } from '@red-hat-developer-hub/backstage-plugin-catalog-model-ai-model-server';\n\nconst TECHDOCS_KEY = 'techdocs';\n\nconst SYSTEM_ANNOTATION = 'rhdh.io/system';\nconst SERVER_TYPE_ANNOTATION = 'rhdh.io/serverType';\nconst DEFAULT_ANNOTATION = 'rhdh.io/default';\nconst OWNER_ANNOTATION = 'rhdh.io/owner';\nconst LIFECYCLE_ANNOTATION = 'rhdh.io/lifecycle';\nconst API_ENTITY_REF_ANNOTATION = 'rhdh.io/api-entity-ref';\n\nfunction isModelCatalog(o: any): o is ModelCatalog {\n return 'models' in o || 'modelServer' in o;\n}\n\nexport function ParseCatalogJSON(jsonStr: string): ModelCatalog {\n const modelCatalog: ModelCatalog = JSON.parse(jsonStr);\n if (isModelCatalog(modelCatalog)) {\n return modelCatalog;\n }\n throw new Error(`model catalog JSON in unexpected format: ${jsonStr}`);\n}\n\nexport function GenerateCatalogEntities(\n modelCatalog: ModelCatalog,\n svcUrl?: string,\n logger?: LoggerService,\n): Entity[] {\n if (!modelCatalog.modelServer) {\n logger?.debug(\n 'ModelCatalog has no modelServer; skipping AiModelServerAPI generation',\n );\n return [];\n }\n\n if (!modelCatalog.modelServer.API?.url) {\n logger?.debug(\n 'ModelCatalog modelServer has no API url; skipping AiModelServerAPI generation',\n );\n return [];\n }\n\n const modelServer = modelCatalog.modelServer;\n const models = modelCatalog.models;\n\n const tags = sanitizeTags(modelServer.tags ?? [], logger);\n if (modelServer.authentication === undefined || !modelServer.authentication) {\n tags.push('auth-not-required');\n } else {\n tags.push('auth-required');\n }\n\n const links: Array<{ title: string; url: string }> = [];\n if (modelServer.API) {\n links.push({ title: 'API', url: modelServer.API.url });\n }\n if (modelServer.homepageURL) {\n links.push({ title: 'Homepage', url: modelServer.homepageURL });\n }\n\n const annotations: Record<string, string> = {};\n\n // Copy API annotations\n if (modelServer.API?.annotations) {\n Object.assign(annotations, modelServer.API.annotations);\n }\n // Copy model server annotations (overrides API on conflict)\n if (modelServer.annotations) {\n Object.assign(annotations, modelServer.annotations);\n }\n\n // Remove control annotations that drive spec fields — they should\n // not leak into entity metadata.annotations.\n delete annotations[SYSTEM_ANNOTATION];\n delete annotations[SERVER_TYPE_ANNOTATION];\n delete annotations[DEFAULT_ANNOTATION];\n delete annotations[OWNER_ANNOTATION];\n delete annotations[LIFECYCLE_ANNOTATION];\n delete annotations[API_ENTITY_REF_ANNOTATION];\n\n // Collect techdocs from models — use the first one found\n for (const model of models) {\n if (model.annotations?.[TECHDOCS_KEY]) {\n let techdocsUrl = model.annotations[TECHDOCS_KEY].trim();\n if (techdocsUrl !== '') {\n if (svcUrl && techdocsUrl.startsWith('/')) {\n techdocsUrl = svcUrl + techdocsUrl;\n }\n annotations['backstage.io/techdocs-ref'] = `url:${techdocsUrl}`;\n break;\n }\n }\n }\n\n // Collect per-model artifact links\n for (const model of models) {\n if (model.artifactLocationURL) {\n links.push({\n title: `${model.name} artifact`,\n url: model.artifactLocationURL,\n });\n }\n }\n\n // Read annotation-driven overrides for spec fields\n const systemOverride = modelServer.annotations?.[SYSTEM_ANNOTATION];\n const serverTypeOverride = modelServer.annotations?.[SERVER_TYPE_ANNOTATION];\n const defaultOverride = modelServer.annotations?.[DEFAULT_ANNOTATION];\n const ownerOverride = modelServer.annotations?.[OWNER_ANNOTATION];\n const lifecycleOverride = modelServer.annotations?.[LIFECYCLE_ANNOTATION];\n const apiEntityRefRaw = modelServer.annotations?.[API_ENTITY_REF_ANNOTATION];\n const apiEntityRefNormalized = apiEntityRefRaw\n ? normalizeApiEntityRef(apiEntityRefRaw)\n : undefined;\n\n const entity: AiModelServerApiEntity = {\n apiVersion: 'backstage.io/v1alpha1',\n kind: 'AiModelServerAPI',\n metadata: {\n name: sanitizeMetadataName(modelServer.name),\n description: modelServer.description,\n tags,\n links,\n ...(Object.keys(annotations).length > 0 && { annotations }),\n },\n spec: {\n type: 'ai-model-server',\n lifecycle: lifecycleOverride ?? modelServer.lifecycle,\n owner: `user:${ownerOverride ?? modelServer.owner}`,\n ...(systemOverride && { system: systemOverride }),\n ...(apiEntityRefNormalized && {\n apiEntityRef: apiEntityRefNormalized,\n }),\n serverType: serverTypeOverride ?? modelServer.API?.type ?? 'unknown',\n serverUrl: modelServer.API?.url ?? '',\n requiresApiKey: modelServer.authentication ?? false,\n models: {\n available: models.map(m => sanitizeMetadataName(m.name)),\n ...getDefaultModel(defaultOverride, models),\n },\n },\n };\n\n return [entity];\n}\n\nfunction getDefaultModel(\n defaultOverride: string | undefined,\n models: { name: string }[],\n): { default: string } | Record<string, never> {\n if (defaultOverride) {\n return { default: sanitizeMetadataName(defaultOverride) };\n }\n if (models.length > 0) {\n return { default: sanitizeMetadataName(models[0].name) };\n }\n return {};\n}\n\nfunction sanitizeMetadataName(modelName: string): string {\n return modelName.replace(/\\s/g, '');\n}\n\nfunction normalizeApiEntityRef(rawValue: string): string {\n const value = rawValue.trim();\n if (!value) return '';\n // Already fully qualified (e.g. api:default/my-api, api:production/my-api)\n if (value.includes(':')) {\n const kind = value.split(':')[0];\n if (kind !== 'api') return '';\n return value;\n }\n // Namespace-qualified (e.g. default/my-api, production/my-api)\n if (value.includes('/')) {\n return `api:${value}`;\n }\n // Bare name (e.g. my-api)\n return `api:default/${value}`;\n}\n\nfunction sanitizeTags(tags: string[], logger?: LoggerService): string[] {\n const sanitizedTags: string[] = [];\n tags.forEach(tag => {\n let sanitizedTag: string = tag;\n sanitizedTag = sanitizedTag.replace(/\\s/g, '');\n\n if (!makeValidator().isValidTag(sanitizedTag)) {\n if (logger !== undefined) {\n logger.error(\n `invalid tag: ${sanitizedTag}. Tags are expected to be less than 63 characters and conform to: ^[a-z0-9:+#]+(\\-[a-z0-9:+#]+)*$`,\n );\n }\n return;\n }\n sanitizedTags.push(sanitizedTag);\n });\n return sanitizedTags;\n}\n"],"names":["makeValidator"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@red-hat-developer-hub/backstage-plugin-catalog-backend-module-model-catalog",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "The model-catalog backend module for the catalog plugin.",
|
|
6
6
|
"main": "dist/index.cjs.js",
|
|
@@ -36,22 +36,22 @@
|
|
|
36
36
|
"postpack": "backstage-cli package postpack"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@backstage/backend-plugin-api": "^1.
|
|
40
|
-
"@backstage/catalog-client": "^1.16.
|
|
41
|
-
"@backstage/catalog-model": "^1.
|
|
39
|
+
"@backstage/backend-plugin-api": "^1.10.0",
|
|
40
|
+
"@backstage/catalog-client": "^1.16.1",
|
|
41
|
+
"@backstage/catalog-model": "^1.10.0",
|
|
42
42
|
"@backstage/errors": "^1.3.1",
|
|
43
|
-
"@backstage/plugin-catalog-backend": "^3.
|
|
43
|
+
"@backstage/plugin-catalog-backend": "^3.9.0",
|
|
44
44
|
"@backstage/plugin-catalog-common": "^1.1.10",
|
|
45
|
-
"@backstage/plugin-catalog-node": "^2.2.
|
|
46
|
-
"@red-hat-developer-hub/backstage-plugin-catalog-model-ai-model-server": "^0.2.
|
|
45
|
+
"@backstage/plugin-catalog-node": "^2.2.4",
|
|
46
|
+
"@red-hat-developer-hub/backstage-plugin-catalog-model-ai-model-server": "^0.2.1",
|
|
47
47
|
"@redhat-ai-dev/model-catalog-types": "^1.1.0",
|
|
48
48
|
"js-yaml": "^4.1.0",
|
|
49
49
|
"stream": "^0.0.3",
|
|
50
50
|
"yaml": "^2.7.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@backstage/backend-test-utils": "^1.11.
|
|
54
|
-
"@backstage/cli": "^0.36.
|
|
53
|
+
"@backstage/backend-test-utils": "^1.11.6",
|
|
54
|
+
"@backstage/cli": "^0.36.5",
|
|
55
55
|
"@backstage/config": "^1.3.8"
|
|
56
56
|
},
|
|
57
57
|
"files": [
|