@backstage-community/plugin-catalog-backend-module-keycloak 3.9.0 → 3.10.1
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 +39 -0
- package/dist/index.d.ts +4 -1
- package/dist/lib/read.cjs.js +10 -3
- package/dist/lib/read.cjs.js.map +1 -1
- package/dist/providers/KeycloakOrgEntityProvider.cjs.js +22 -2
- package/dist/providers/KeycloakOrgEntityProvider.cjs.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
### Dependencies
|
|
2
2
|
|
|
3
|
+
## 3.10.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f84ad73: chore: remove homepage field from package.json
|
|
8
|
+
|
|
9
|
+
## 3.10.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 797add4: Implement open-telemetry metrics for catalog backend module keycloak.
|
|
14
|
+
|
|
3
15
|
## 3.9.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -136,6 +136,45 @@ When using client credentials, the access type must be set to `confidential` and
|
|
|
136
136
|
- `query-users`
|
|
137
137
|
- `view-users`
|
|
138
138
|
|
|
139
|
+
### 📊 Metrics
|
|
140
|
+
|
|
141
|
+
The Keycloak backend plugin supports [OpenTelemetry](https://opentelemetry.io/) metrics to monitor fetch operations and diagnose potential issues.
|
|
142
|
+
|
|
143
|
+
#### Available Counters
|
|
144
|
+
|
|
145
|
+
| Metric Name | Description |
|
|
146
|
+
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
147
|
+
| `backend_keycloak_fetch_task_failure_count_total` | Counts fetch task failures where no data was returned due to an error. |
|
|
148
|
+
| `backend_keycloak_fetch_data_batch_failure_count_total` | Counts partial data batch failures. Even if some batches fail, the plugin continues fetching others. |
|
|
149
|
+
|
|
150
|
+
The plugin is designed to tolerate partial failures and proceed with the data that can be successfully retrieved. Failed batches may be fetched in the next scheduled task.
|
|
151
|
+
|
|
152
|
+
#### 🏷 Labels
|
|
153
|
+
|
|
154
|
+
All counters include the `taskInstanceId` label, which uniquely identifies each scheduled fetch task.
|
|
155
|
+
This allows you to trace failures back to individual task executions.
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
backend_keycloak_fetch_data_batch_failure_count_total{taskInstanceId="df040f82-2e80-44bd-83b0-06a984ca05ba"} 1
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### ⚠️ Use Case Example
|
|
164
|
+
|
|
165
|
+
Imagine your Keycloak instance is under-provisioned (e.g., low CPU/RAM limits), and the plugin is configured to send many parallel API requests.
|
|
166
|
+
This could cause request timeouts or throttling. The metrics described above can help detect such behavior early, allowing administrators to:
|
|
167
|
+
|
|
168
|
+
- Tune the plugin configuration (e.g., reduce parallelism)
|
|
169
|
+
- Increase resources on the Keycloak server
|
|
170
|
+
- Investigate network or permission issues
|
|
171
|
+
|
|
172
|
+
#### 📥 Exporting Metrics
|
|
173
|
+
|
|
174
|
+
You can export metrics using any OpenTelemetry-compatible backend, such as **Prometheus**.
|
|
175
|
+
|
|
176
|
+
➡️ See the [Backstage OpenTelemetry setup guide](https://backstage.io/docs/tutorials/setup-opentelemetry) for integration instructions.
|
|
177
|
+
|
|
139
178
|
### Limitations
|
|
140
179
|
|
|
141
180
|
If you have self-signed or corporate certificate issues, you can set the following environment variable before starting Backstage:
|
package/dist/index.d.ts
CHANGED
|
@@ -193,6 +193,8 @@ interface KeycloakOrgEntityProviderOptions {
|
|
|
193
193
|
declare class KeycloakOrgEntityProvider implements EntityProvider {
|
|
194
194
|
private options;
|
|
195
195
|
private connection?;
|
|
196
|
+
private meter;
|
|
197
|
+
private counter;
|
|
196
198
|
private scheduleFn?;
|
|
197
199
|
/**
|
|
198
200
|
* Static builder method to create multiple KeycloakOrgEntityProvider instances from a single config.
|
|
@@ -232,8 +234,9 @@ declare class KeycloakOrgEntityProvider implements EntityProvider {
|
|
|
232
234
|
* Runs one complete ingestion loop. Call this method regularly at some
|
|
233
235
|
* appropriate cadence.
|
|
234
236
|
*/
|
|
235
|
-
read(options
|
|
237
|
+
read(options: {
|
|
236
238
|
logger?: LoggerService;
|
|
239
|
+
taskInstanceId: string;
|
|
237
240
|
}): Promise<void>;
|
|
238
241
|
/**
|
|
239
242
|
* Periodically schedules a task to read Keycloak user and group information, parse it, and provision it to the Backstage catalog.
|
package/dist/lib/read.cjs.js
CHANGED
|
@@ -53,7 +53,7 @@ const parseUser = async (user, realm, keycloakGroups, groupIndex, userTransforme
|
|
|
53
53
|
};
|
|
54
54
|
return await transformer(entity, user, realm, keycloakGroups);
|
|
55
55
|
};
|
|
56
|
-
async function getEntities(getEntitiesFn, config, logger, limit, entityQuerySize = constants.KEYCLOAK_ENTITY_QUERY_SIZE) {
|
|
56
|
+
async function getEntities(getEntitiesFn, config, logger, dataBatchFailureCounter, taskInstanceId, limit, entityQuerySize = constants.KEYCLOAK_ENTITY_QUERY_SIZE) {
|
|
57
57
|
const entitiesAPI = await getEntitiesFn();
|
|
58
58
|
const rawEntityCount = await entitiesAPI.count({ realm: config.realm });
|
|
59
59
|
const entityCount = typeof rawEntityCount === "number" ? rawEntityCount : rawEntityCount.count;
|
|
@@ -74,7 +74,10 @@ async function getEntities(getEntitiesFn, config, logger, limit, entityQuerySize
|
|
|
74
74
|
);
|
|
75
75
|
return ents;
|
|
76
76
|
}).catch((err) => {
|
|
77
|
-
|
|
77
|
+
dataBatchFailureCounter.add(1, { taskInstanceId });
|
|
78
|
+
logger.warn(
|
|
79
|
+
`Failed to retieve Keycloak entities for taskInstanceId: ${taskInstanceId}. Error: ${err}`
|
|
80
|
+
);
|
|
78
81
|
return [];
|
|
79
82
|
});
|
|
80
83
|
})
|
|
@@ -137,7 +140,7 @@ function* traverseGroups(group) {
|
|
|
137
140
|
yield* traverseGroups(g);
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
|
-
const readKeycloakRealm = async (client, config, logger, limit, options) => {
|
|
143
|
+
const readKeycloakRealm = async (client, config, logger, limit, taskInstanceId, dataBatchFailureCounter, options) => {
|
|
141
144
|
const kUsers = await getEntities(
|
|
142
145
|
async () => {
|
|
143
146
|
await authenticate.ensureTokenValid(client, config, logger);
|
|
@@ -145,6 +148,8 @@ const readKeycloakRealm = async (client, config, logger, limit, options) => {
|
|
|
145
148
|
},
|
|
146
149
|
config,
|
|
147
150
|
logger,
|
|
151
|
+
dataBatchFailureCounter,
|
|
152
|
+
taskInstanceId,
|
|
148
153
|
limit,
|
|
149
154
|
options?.userQuerySize
|
|
150
155
|
);
|
|
@@ -156,6 +161,8 @@ const readKeycloakRealm = async (client, config, logger, limit, options) => {
|
|
|
156
161
|
},
|
|
157
162
|
config,
|
|
158
163
|
logger,
|
|
164
|
+
dataBatchFailureCounter,
|
|
165
|
+
taskInstanceId,
|
|
159
166
|
limit,
|
|
160
167
|
options?.groupQuerySize
|
|
161
168
|
);
|
package/dist/lib/read.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.cjs.js","sources":["../../src/lib/read.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { LoggerService } from '@backstage/backend-plugin-api';\nimport type { GroupEntity, UserEntity } from '@backstage/catalog-model';\n\nimport type KeycloakAdminClient from '@keycloak/keycloak-admin-client';\nimport type GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation';\nimport type UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation';\nimport type { Groups } from '@keycloak/keycloak-admin-client/lib/resources/groups';\nimport type { Users } from '@keycloak/keycloak-admin-client/lib/resources/users';\nimport { LimitFunction } from 'p-limit';\n\nimport { KeycloakProviderConfig } from './config';\nimport {\n KEYCLOAK_ENTITY_QUERY_SIZE,\n KEYCLOAK_ID_ANNOTATION,\n KEYCLOAK_REALM_ANNOTATION,\n KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT,\n} from './constants';\nimport { noopGroupTransformer, noopUserTransformer } from './transformers';\nimport {\n GroupRepresentationWithParent,\n GroupRepresentationWithParentAndEntity,\n GroupTransformer,\n UserRepresentationWithEntity,\n UserTransformer,\n} from './types';\nimport { ensureTokenValid } from './authenticate';\n\nexport const parseGroup = async (\n keycloakGroup: GroupRepresentationWithParent,\n realm: string,\n groupTransformer?: GroupTransformer,\n): Promise<GroupEntity | undefined> => {\n const transformer = groupTransformer ?? noopGroupTransformer;\n const entity: GroupEntity = {\n apiVersion: 'backstage.io/v1beta1',\n kind: 'Group',\n metadata: {\n name: keycloakGroup.name!,\n annotations: {\n [KEYCLOAK_ID_ANNOTATION]: keycloakGroup.id!,\n [KEYCLOAK_REALM_ANNOTATION]: realm,\n },\n },\n spec: {\n type: 'group',\n profile: {\n displayName: keycloakGroup.name!,\n },\n // children, parent and members are updated again after all group and user transformers applied.\n children: keycloakGroup.subGroups?.map(g => g.name!) ?? [],\n parent: keycloakGroup.parent,\n members: keycloakGroup.members,\n },\n };\n\n return await transformer(entity, keycloakGroup, realm);\n};\n\nexport const parseUser = async (\n user: UserRepresentation,\n realm: string,\n keycloakGroups: GroupRepresentationWithParentAndEntity[],\n groupIndex: Map<string, string[]>,\n userTransformer?: UserTransformer,\n): Promise<UserEntity | undefined> => {\n const transformer = userTransformer ?? noopUserTransformer;\n const entity: UserEntity = {\n apiVersion: 'backstage.io/v1beta1',\n kind: 'User',\n metadata: {\n name: user.username!,\n annotations: {\n [KEYCLOAK_ID_ANNOTATION]: user.id!,\n [KEYCLOAK_REALM_ANNOTATION]: realm,\n },\n },\n spec: {\n profile: {\n email: user.email,\n ...(user.firstName || user.lastName\n ? {\n displayName: [user.firstName, user.lastName]\n .filter(Boolean)\n .join(' '),\n }\n : {}),\n },\n memberOf: groupIndex.get(user.username!) ?? [],\n },\n };\n\n return await transformer(entity, user, realm, keycloakGroups);\n};\n\nexport async function getEntities<T extends Users | Groups>(\n getEntitiesFn: () => Promise<T>,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n limit: LimitFunction,\n entityQuerySize: number = KEYCLOAK_ENTITY_QUERY_SIZE,\n): Promise<Awaited<ReturnType<T['find']>>> {\n const entitiesAPI = await getEntitiesFn();\n const rawEntityCount = await entitiesAPI.count({ realm: config.realm });\n const entityCount =\n typeof rawEntityCount === 'number' ? rawEntityCount : rawEntityCount.count;\n\n const pageCount = Math.ceil(entityCount / entityQuerySize);\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n // The next line acts like range in python\n const entityPromises = Array.from({ length: pageCount }, (_, i) =>\n limit(() =>\n getEntitiesFn().then(entities => {\n return entities\n .find({\n realm: config.realm,\n max: entityQuerySize,\n first: i * entityQuerySize,\n briefRepresentation: brief,\n })\n .then(ents => {\n logger.debug(\n `Importing keycloak entities batch with index ${i} from pages: ${pageCount}`,\n );\n return ents;\n })\n .catch(err => {\n logger.warn('Failed to retieve Keycloak entities.', err);\n return [];\n }) as ReturnType<T['find']>;\n }),\n ),\n );\n\n const entityResults = (await Promise.all(entityPromises)).flat() as Awaited<\n ReturnType<T['find']>\n >;\n\n return entityResults;\n}\n\nasync function getAllGroupMembers<T extends Groups>(\n groupsAPI: () => Promise<T>,\n groupId: string,\n config: KeycloakProviderConfig,\n options?: { userQuerySize?: number },\n): Promise<string[]> {\n const querySize = options?.userQuerySize || 100;\n\n let allMembers: string[] = [];\n let page = 0;\n let totalMembers = 0;\n\n do {\n const groups = await groupsAPI();\n const members = await groups.listMembers({\n id: groupId,\n max: querySize,\n realm: config.realm,\n first: page * querySize,\n });\n\n if (members.length > 0) {\n allMembers = allMembers.concat(members.map(m => m.username!));\n totalMembers = members.length; // Get the number of members retrieved\n } else {\n totalMembers = 0; // No members retrieved\n }\n\n page++;\n } while (totalMembers > 0);\n\n return allMembers;\n}\n\nexport async function processGroupsRecursively(\n kcAdminClient: KeycloakAdminClient,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n topLevelGroups: GroupRepresentationWithParent[],\n) {\n const allGroups: GroupRepresentationWithParent[] = [];\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n for (const group of topLevelGroups) {\n allGroups.push(group);\n\n if (group.subGroupCount! > 0) {\n await ensureTokenValid(kcAdminClient, config, logger);\n const subgroups = await kcAdminClient.groups.listSubGroups({\n parentId: group.id!,\n first: 0,\n max: group.subGroupCount,\n briefRepresentation: brief,\n });\n const subGroupResults = await processGroupsRecursively(\n kcAdminClient,\n config,\n logger,\n subgroups,\n );\n allGroups.push(...subGroupResults);\n }\n }\n\n return allGroups;\n}\n\nexport function* traverseGroups(\n group: GroupRepresentation,\n): IterableIterator<GroupRepresentationWithParent> {\n yield group;\n for (const g of group.subGroups ?? []) {\n (g as GroupRepresentationWithParent).parent = group.name!;\n yield* traverseGroups(g);\n }\n}\n\nexport const readKeycloakRealm = async (\n client: KeycloakAdminClient,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n limit: LimitFunction,\n options?: {\n userQuerySize?: number;\n groupQuerySize?: number;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n): Promise<{\n users: UserEntity[];\n groups: GroupEntity[];\n}> => {\n const kUsers = await getEntities(\n async () => {\n await ensureTokenValid(client, config, logger);\n return client.users;\n },\n config,\n logger,\n limit,\n options?.userQuerySize,\n );\n logger.debug(`Fetched ${kUsers.length} users from Keycloak`);\n\n const topLevelKGroups = (await getEntities(\n async () => {\n await ensureTokenValid(client, config, logger);\n return client.groups;\n },\n config,\n logger,\n limit,\n options?.groupQuerySize,\n )) as GroupRepresentationWithParent[];\n logger.debug(`Fetched ${topLevelKGroups.length} groups from Keycloak`);\n\n let serverVersion: number;\n\n try {\n await ensureTokenValid(client, config, logger);\n const serverInfo = await client.serverInfo.find();\n serverVersion = parseInt(\n serverInfo.systemInfo?.version?.slice(0, 2) || '',\n 10,\n );\n } catch (error) {\n throw new Error(`Failed to retrieve Keycloak server information: ${error}`);\n }\n\n const isVersion23orHigher = serverVersion >= 23;\n\n let rawKGroups: GroupRepresentationWithParent[] = [];\n\n logger.debug(`Processing groups recursively`);\n if (isVersion23orHigher) {\n rawKGroups = await processGroupsRecursively(\n client,\n config,\n logger,\n topLevelKGroups,\n );\n } else {\n rawKGroups = topLevelKGroups.reduce(\n (acc, g) => acc.concat(...traverseGroups(g)),\n [] as GroupRepresentationWithParent[],\n );\n }\n\n logger.debug(`Fetching group members for keycloak groups and list subgroups`);\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n const kGroups = await Promise.all(\n rawKGroups.map(g =>\n limit(async () => {\n g.members = await getAllGroupMembers(\n async () => {\n await ensureTokenValid(client, config, logger);\n return client.groups as Groups;\n },\n g.id!,\n config,\n options,\n );\n\n if (isVersion23orHigher) {\n if (g.subGroupCount! > 0) {\n await ensureTokenValid(client, config, logger);\n g.subGroups = await client.groups.listSubGroups({\n parentId: g.id!,\n first: 0,\n max: g.subGroupCount,\n briefRepresentation: brief,\n realm: config.realm,\n });\n }\n if (g.parentId) {\n await ensureTokenValid(client, config, logger);\n const groupParent = await client.groups.findOne({\n id: g.parentId,\n realm: config.realm,\n });\n g.parent = groupParent?.name;\n }\n }\n\n return g;\n }),\n ),\n );\n\n logger.debug(`Parsing groups`);\n const parsedGroups = await Promise.all(\n kGroups.map(async g => {\n // it is possible if fetch request failed\n if (!g) {\n return null;\n }\n const entity = await parseGroup(\n g,\n config.realm,\n options?.groupTransformer,\n );\n if (entity) {\n return { ...g, entity } as GroupRepresentationWithParentAndEntity;\n }\n return null;\n }),\n );\n const filteredParsedGroups = parsedGroups.filter(\n (group): group is GroupRepresentationWithParentAndEntity => group !== null,\n );\n\n const groupIndex = new Map<string, string[]>();\n filteredParsedGroups.forEach(group => {\n if (group.members) {\n group.members.forEach(member => {\n if (!groupIndex.has(member)) {\n groupIndex.set(member, []);\n }\n groupIndex.get(member)?.push(group.entity.metadata.name);\n });\n }\n });\n\n logger.debug('Parsing users');\n const parsedUsers = await Promise.all(\n kUsers.map(async u => {\n // it is possible if fetch request failed\n if (!u) {\n return null;\n }\n const entity = await parseUser(\n u,\n config.realm,\n filteredParsedGroups,\n groupIndex,\n options?.userTransformer,\n );\n if (entity) {\n return { ...u, entity } as UserRepresentationWithEntity;\n }\n return null;\n }),\n );\n const filteredParsedUsers = parsedUsers.filter(\n (user): user is UserRepresentationWithEntity => user !== null,\n );\n\n logger.debug(`Set up group members and children information`);\n\n const userMap = new Map(\n filteredParsedUsers.map(user => [user.username, user.entity.metadata.name]),\n );\n\n const groupMap = new Map(\n filteredParsedGroups.map(group => [group.name, group.entity.metadata.name]),\n );\n\n const groups = filteredParsedGroups.map(g => {\n const entity = g.entity;\n entity.spec.members =\n g.entity.spec.members?.flatMap(m => userMap.get(m) ?? []) ?? [];\n entity.spec.children =\n g.entity.spec.children?.flatMap(c => groupMap.get(c) ?? []) ?? [];\n entity.spec.parent = groupMap.get(entity.spec.parent);\n return entity;\n });\n\n logger.info(\n `Prepared to ingest ${parsedUsers.length} users and ${groups.length} groups into the catalog from Keycloak`,\n );\n\n return { users: filteredParsedUsers.map(u => u.entity), groups };\n};\n"],"names":["noopGroupTransformer","KEYCLOAK_ID_ANNOTATION","KEYCLOAK_REALM_ANNOTATION","noopUserTransformer","KEYCLOAK_ENTITY_QUERY_SIZE","KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT","ensureTokenValid"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"read.cjs.js","sources":["../../src/lib/read.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { LoggerService } from '@backstage/backend-plugin-api';\nimport type { GroupEntity, UserEntity } from '@backstage/catalog-model';\n\nimport type KeycloakAdminClient from '@keycloak/keycloak-admin-client';\nimport type GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation';\nimport type UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation';\nimport type { Groups } from '@keycloak/keycloak-admin-client/lib/resources/groups';\nimport type { Users } from '@keycloak/keycloak-admin-client/lib/resources/users';\nimport { LimitFunction } from 'p-limit';\n\nimport { KeycloakProviderConfig } from './config';\nimport {\n KEYCLOAK_ENTITY_QUERY_SIZE,\n KEYCLOAK_ID_ANNOTATION,\n KEYCLOAK_REALM_ANNOTATION,\n KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT,\n} from './constants';\nimport { noopGroupTransformer, noopUserTransformer } from './transformers';\nimport {\n GroupRepresentationWithParent,\n GroupRepresentationWithParentAndEntity,\n GroupTransformer,\n UserRepresentationWithEntity,\n UserTransformer,\n} from './types';\nimport { ensureTokenValid } from './authenticate';\nimport { Attributes, Counter } from '@opentelemetry/api';\n\nexport const parseGroup = async (\n keycloakGroup: GroupRepresentationWithParent,\n realm: string,\n groupTransformer?: GroupTransformer,\n): Promise<GroupEntity | undefined> => {\n const transformer = groupTransformer ?? noopGroupTransformer;\n const entity: GroupEntity = {\n apiVersion: 'backstage.io/v1beta1',\n kind: 'Group',\n metadata: {\n name: keycloakGroup.name!,\n annotations: {\n [KEYCLOAK_ID_ANNOTATION]: keycloakGroup.id!,\n [KEYCLOAK_REALM_ANNOTATION]: realm,\n },\n },\n spec: {\n type: 'group',\n profile: {\n displayName: keycloakGroup.name!,\n },\n // children, parent and members are updated again after all group and user transformers applied.\n children: keycloakGroup.subGroups?.map(g => g.name!) ?? [],\n parent: keycloakGroup.parent,\n members: keycloakGroup.members,\n },\n };\n\n return await transformer(entity, keycloakGroup, realm);\n};\n\nexport const parseUser = async (\n user: UserRepresentation,\n realm: string,\n keycloakGroups: GroupRepresentationWithParentAndEntity[],\n groupIndex: Map<string, string[]>,\n userTransformer?: UserTransformer,\n): Promise<UserEntity | undefined> => {\n const transformer = userTransformer ?? noopUserTransformer;\n const entity: UserEntity = {\n apiVersion: 'backstage.io/v1beta1',\n kind: 'User',\n metadata: {\n name: user.username!,\n annotations: {\n [KEYCLOAK_ID_ANNOTATION]: user.id!,\n [KEYCLOAK_REALM_ANNOTATION]: realm,\n },\n },\n spec: {\n profile: {\n email: user.email,\n ...(user.firstName || user.lastName\n ? {\n displayName: [user.firstName, user.lastName]\n .filter(Boolean)\n .join(' '),\n }\n : {}),\n },\n memberOf: groupIndex.get(user.username!) ?? [],\n },\n };\n\n return await transformer(entity, user, realm, keycloakGroups);\n};\n\nexport async function getEntities<T extends Users | Groups>(\n getEntitiesFn: () => Promise<T>,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n dataBatchFailureCounter: Counter<Attributes>,\n taskInstanceId: string,\n limit: LimitFunction,\n entityQuerySize: number = KEYCLOAK_ENTITY_QUERY_SIZE,\n): Promise<Awaited<ReturnType<T['find']>>> {\n const entitiesAPI = await getEntitiesFn();\n const rawEntityCount = await entitiesAPI.count({ realm: config.realm });\n const entityCount =\n typeof rawEntityCount === 'number' ? rawEntityCount : rawEntityCount.count;\n\n const pageCount = Math.ceil(entityCount / entityQuerySize);\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n // The next line acts like range in python\n const entityPromises = Array.from({ length: pageCount }, (_, i) =>\n limit(() =>\n getEntitiesFn().then(entities => {\n return entities\n .find({\n realm: config.realm,\n max: entityQuerySize,\n first: i * entityQuerySize,\n briefRepresentation: brief,\n })\n .then(ents => {\n logger.debug(\n `Importing keycloak entities batch with index ${i} from pages: ${pageCount}`,\n );\n return ents;\n })\n .catch(err => {\n dataBatchFailureCounter.add(1, { taskInstanceId: taskInstanceId });\n logger.warn(\n `Failed to retieve Keycloak entities for taskInstanceId: ${taskInstanceId}. Error: ${err}`,\n );\n return [];\n }) as ReturnType<T['find']>;\n }),\n ),\n );\n\n const entityResults = (await Promise.all(entityPromises)).flat() as Awaited<\n ReturnType<T['find']>\n >;\n\n return entityResults;\n}\n\nasync function getAllGroupMembers<T extends Groups>(\n groupsAPI: () => Promise<T>,\n groupId: string,\n config: KeycloakProviderConfig,\n options?: { userQuerySize?: number },\n): Promise<string[]> {\n const querySize = options?.userQuerySize || 100;\n\n let allMembers: string[] = [];\n let page = 0;\n let totalMembers = 0;\n\n do {\n const groups = await groupsAPI();\n const members = await groups.listMembers({\n id: groupId,\n max: querySize,\n realm: config.realm,\n first: page * querySize,\n });\n\n if (members.length > 0) {\n allMembers = allMembers.concat(members.map(m => m.username!));\n totalMembers = members.length; // Get the number of members retrieved\n } else {\n totalMembers = 0; // No members retrieved\n }\n\n page++;\n } while (totalMembers > 0);\n\n return allMembers;\n}\n\nexport async function processGroupsRecursively(\n kcAdminClient: KeycloakAdminClient,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n topLevelGroups: GroupRepresentationWithParent[],\n) {\n const allGroups: GroupRepresentationWithParent[] = [];\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n for (const group of topLevelGroups) {\n allGroups.push(group);\n\n if (group.subGroupCount! > 0) {\n await ensureTokenValid(kcAdminClient, config, logger);\n const subgroups = await kcAdminClient.groups.listSubGroups({\n parentId: group.id!,\n first: 0,\n max: group.subGroupCount,\n briefRepresentation: brief,\n });\n const subGroupResults = await processGroupsRecursively(\n kcAdminClient,\n config,\n logger,\n subgroups,\n );\n allGroups.push(...subGroupResults);\n }\n }\n\n return allGroups;\n}\n\nexport function* traverseGroups(\n group: GroupRepresentation,\n): IterableIterator<GroupRepresentationWithParent> {\n yield group;\n for (const g of group.subGroups ?? []) {\n (g as GroupRepresentationWithParent).parent = group.name!;\n yield* traverseGroups(g);\n }\n}\n\nexport const readKeycloakRealm = async (\n client: KeycloakAdminClient,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n limit: LimitFunction,\n taskInstanceId: string,\n dataBatchFailureCounter: Counter<Attributes>,\n options?: {\n userQuerySize?: number;\n groupQuerySize?: number;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n): Promise<{\n users: UserEntity[];\n groups: GroupEntity[];\n}> => {\n const kUsers = await getEntities(\n async () => {\n await ensureTokenValid(client, config, logger);\n return client.users;\n },\n config,\n logger,\n dataBatchFailureCounter,\n taskInstanceId,\n limit,\n options?.userQuerySize,\n );\n logger.debug(`Fetched ${kUsers.length} users from Keycloak`);\n\n const topLevelKGroups = (await getEntities(\n async () => {\n await ensureTokenValid(client, config, logger);\n return client.groups;\n },\n config,\n logger,\n dataBatchFailureCounter,\n taskInstanceId,\n limit,\n options?.groupQuerySize,\n )) as GroupRepresentationWithParent[];\n logger.debug(`Fetched ${topLevelKGroups.length} groups from Keycloak`);\n\n let serverVersion: number;\n\n try {\n await ensureTokenValid(client, config, logger);\n const serverInfo = await client.serverInfo.find();\n serverVersion = parseInt(\n serverInfo.systemInfo?.version?.slice(0, 2) || '',\n 10,\n );\n } catch (error) {\n throw new Error(`Failed to retrieve Keycloak server information: ${error}`);\n }\n\n const isVersion23orHigher = serverVersion >= 23;\n\n let rawKGroups: GroupRepresentationWithParent[] = [];\n\n logger.debug(`Processing groups recursively`);\n if (isVersion23orHigher) {\n rawKGroups = await processGroupsRecursively(\n client,\n config,\n logger,\n topLevelKGroups,\n );\n } else {\n rawKGroups = topLevelKGroups.reduce(\n (acc, g) => acc.concat(...traverseGroups(g)),\n [] as GroupRepresentationWithParent[],\n );\n }\n\n logger.debug(`Fetching group members for keycloak groups and list subgroups`);\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n const kGroups = await Promise.all(\n rawKGroups.map(g =>\n limit(async () => {\n g.members = await getAllGroupMembers(\n async () => {\n await ensureTokenValid(client, config, logger);\n return client.groups as Groups;\n },\n g.id!,\n config,\n options,\n );\n\n if (isVersion23orHigher) {\n if (g.subGroupCount! > 0) {\n await ensureTokenValid(client, config, logger);\n g.subGroups = await client.groups.listSubGroups({\n parentId: g.id!,\n first: 0,\n max: g.subGroupCount,\n briefRepresentation: brief,\n realm: config.realm,\n });\n }\n if (g.parentId) {\n await ensureTokenValid(client, config, logger);\n const groupParent = await client.groups.findOne({\n id: g.parentId,\n realm: config.realm,\n });\n g.parent = groupParent?.name;\n }\n }\n\n return g;\n }),\n ),\n );\n\n logger.debug(`Parsing groups`);\n const parsedGroups = await Promise.all(\n kGroups.map(async g => {\n // it is possible if fetch request failed\n if (!g) {\n return null;\n }\n const entity = await parseGroup(\n g,\n config.realm,\n options?.groupTransformer,\n );\n if (entity) {\n return { ...g, entity } as GroupRepresentationWithParentAndEntity;\n }\n return null;\n }),\n );\n const filteredParsedGroups = parsedGroups.filter(\n (group): group is GroupRepresentationWithParentAndEntity => group !== null,\n );\n\n const groupIndex = new Map<string, string[]>();\n filteredParsedGroups.forEach(group => {\n if (group.members) {\n group.members.forEach(member => {\n if (!groupIndex.has(member)) {\n groupIndex.set(member, []);\n }\n groupIndex.get(member)?.push(group.entity.metadata.name);\n });\n }\n });\n\n logger.debug('Parsing users');\n const parsedUsers = await Promise.all(\n kUsers.map(async u => {\n // it is possible if fetch request failed\n if (!u) {\n return null;\n }\n const entity = await parseUser(\n u,\n config.realm,\n filteredParsedGroups,\n groupIndex,\n options?.userTransformer,\n );\n if (entity) {\n return { ...u, entity } as UserRepresentationWithEntity;\n }\n return null;\n }),\n );\n const filteredParsedUsers = parsedUsers.filter(\n (user): user is UserRepresentationWithEntity => user !== null,\n );\n\n logger.debug(`Set up group members and children information`);\n\n const userMap = new Map(\n filteredParsedUsers.map(user => [user.username, user.entity.metadata.name]),\n );\n\n const groupMap = new Map(\n filteredParsedGroups.map(group => [group.name, group.entity.metadata.name]),\n );\n\n const groups = filteredParsedGroups.map(g => {\n const entity = g.entity;\n entity.spec.members =\n g.entity.spec.members?.flatMap(m => userMap.get(m) ?? []) ?? [];\n entity.spec.children =\n g.entity.spec.children?.flatMap(c => groupMap.get(c) ?? []) ?? [];\n entity.spec.parent = groupMap.get(entity.spec.parent);\n return entity;\n });\n\n logger.info(\n `Prepared to ingest ${parsedUsers.length} users and ${groups.length} groups into the catalog from Keycloak`,\n );\n\n return { users: filteredParsedUsers.map(u => u.entity), groups };\n};\n"],"names":["noopGroupTransformer","KEYCLOAK_ID_ANNOTATION","KEYCLOAK_REALM_ANNOTATION","noopUserTransformer","KEYCLOAK_ENTITY_QUERY_SIZE","KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT","ensureTokenValid"],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ var constants = require('../lib/constants.cjs.js');
|
|
|
8
8
|
var config = require('../lib/config.cjs.js');
|
|
9
9
|
var read = require('../lib/read.cjs.js');
|
|
10
10
|
var authenticate = require('../lib/authenticate.cjs.js');
|
|
11
|
+
var api = require('@opentelemetry/api');
|
|
11
12
|
|
|
12
13
|
function _interopNamespaceCompat(e) {
|
|
13
14
|
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
@@ -47,9 +48,18 @@ const withLocations = (baseUrl, realm, entity) => {
|
|
|
47
48
|
class KeycloakOrgEntityProvider {
|
|
48
49
|
constructor(options) {
|
|
49
50
|
this.options = options;
|
|
51
|
+
this.meter = api.metrics.getMeter("default");
|
|
52
|
+
this.counter = this.meter.createCounter(
|
|
53
|
+
"backend_keycloak.fetch.task.failure.count",
|
|
54
|
+
{
|
|
55
|
+
description: "Counts the number of failed Keycloak data fetch tasks. Each increment indicates a complete failure of a fetch task, meaning no data was provided to the Catalog API. However, data may still be fetched in subsequent tasks, depending on the nature of the error."
|
|
56
|
+
}
|
|
57
|
+
);
|
|
50
58
|
this.schedule(options.taskRunner);
|
|
51
59
|
}
|
|
52
60
|
connection;
|
|
61
|
+
meter;
|
|
62
|
+
counter;
|
|
53
63
|
scheduleFn;
|
|
54
64
|
/**
|
|
55
65
|
* Static builder method to create multiple KeycloakOrgEntityProvider instances from a single config.
|
|
@@ -119,11 +129,19 @@ class KeycloakOrgEntityProvider {
|
|
|
119
129
|
const limitFunc = pLimitCJSModule.default;
|
|
120
130
|
const concurrency = provider.maxConcurrency ?? 20;
|
|
121
131
|
const limit = limitFunc(concurrency);
|
|
132
|
+
const dataBatchFailureCounter = this.meter.createCounter(
|
|
133
|
+
"backend_keycloak.fetch.data.batch.failure.count",
|
|
134
|
+
{
|
|
135
|
+
description: "Keycloak data batch fetch failure counter. Incremented for each batch fetch failure. Each failure means that a part of the data was not fetched due to an error, and thus the corresponding data batch was skipped during the current fetch task."
|
|
136
|
+
}
|
|
137
|
+
);
|
|
122
138
|
const { users, groups } = await read.readKeycloakRealm(
|
|
123
139
|
kcAdminClient,
|
|
124
140
|
provider,
|
|
125
141
|
logger,
|
|
126
142
|
limit,
|
|
143
|
+
options.taskInstanceId,
|
|
144
|
+
dataBatchFailureCounter,
|
|
127
145
|
{
|
|
128
146
|
userQuerySize: provider.userQuerySize,
|
|
129
147
|
groupQuerySize: provider.groupQuerySize,
|
|
@@ -151,14 +169,16 @@ class KeycloakOrgEntityProvider {
|
|
|
151
169
|
await taskRunner.run({
|
|
152
170
|
id,
|
|
153
171
|
fn: async () => {
|
|
172
|
+
const taskInstanceId = uuid__namespace.v4();
|
|
154
173
|
const logger = this.options.logger.child({
|
|
155
174
|
class: KeycloakOrgEntityProvider.prototype.constructor.name,
|
|
156
175
|
taskId: id,
|
|
157
|
-
taskInstanceId
|
|
176
|
+
taskInstanceId
|
|
158
177
|
});
|
|
159
178
|
try {
|
|
160
|
-
await this.read({ logger });
|
|
179
|
+
await this.read({ logger, taskInstanceId });
|
|
161
180
|
} catch (error) {
|
|
181
|
+
this.counter.add(1, { taskInstanceId });
|
|
162
182
|
if (errors.isError(error)) {
|
|
163
183
|
logger.error("Error while syncing Keycloak users and groups", {
|
|
164
184
|
// Default Error properties:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KeycloakOrgEntityProvider.cjs.js","sources":["../../src/providers/KeycloakOrgEntityProvider.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n LoggerService,\n SchedulerService,\n SchedulerServiceTaskRunner,\n} from '@backstage/backend-plugin-api';\nimport {\n ANNOTATION_LOCATION,\n ANNOTATION_ORIGIN_LOCATION,\n type Entity,\n} from '@backstage/catalog-model';\nimport type { Config } from '@backstage/config';\nimport { InputError, isError, NotFoundError } from '@backstage/errors';\nimport type {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\n\n// @ts-ignore\nimport { merge } from 'lodash';\nimport { LimitFunction } from 'p-limit';\nimport * as uuid from 'uuid';\n\nimport {\n GroupTransformer,\n KEYCLOAK_ID_ANNOTATION,\n KeycloakProviderConfig,\n UserTransformer,\n} from '../lib';\nimport { readProviderConfigs } from '../lib/config';\nimport { readKeycloakRealm } from '../lib/read';\nimport { authenticate } from '../lib/authenticate';\n\n/**\n * Options for {@link KeycloakOrgEntityProvider}.\n *\n * @public\n */\nexport interface KeycloakOrgEntityProviderOptions {\n /**\n * A unique, stable identifier for this provider.\n *\n * @example \"production\"\n */\n id: string;\n\n /**\n * The refresh schedule to use.\n * @remarks\n *\n * You can pass in the result of\n * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner}\n * to enable automatic scheduling of tasks.\n */\n schedule?: SchedulerServiceTaskRunner;\n\n /**\n * Scheduler used to schedule refreshes based on\n * the schedule config.\n */\n scheduler?: SchedulerService;\n\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * The function that transforms a user entry in LDAP to an entity.\n */\n userTransformer?: UserTransformer;\n\n /**\n * The function that transforms a group entry in LDAP to an entity.\n */\n groupTransformer?: GroupTransformer;\n}\n\n// Makes sure that emitted entities have a proper location\nexport const withLocations = (\n baseUrl: string,\n realm: string,\n entity: Entity,\n): Entity => {\n const kind = entity.kind === 'Group' ? 'groups' : 'users';\n const location = `url:${baseUrl}/admin/realms/${realm}/${kind}/${entity.metadata.annotations?.[KEYCLOAK_ID_ANNOTATION]}`;\n return merge(\n {\n metadata: {\n annotations: {\n [ANNOTATION_LOCATION]: location,\n [ANNOTATION_ORIGIN_LOCATION]: location,\n },\n },\n },\n entity,\n ) as Entity;\n};\n\n/**\n * Ingests org data (users and groups) from GitHub.\n *\n * @public\n */\nexport class KeycloakOrgEntityProvider implements EntityProvider {\n private connection?: EntityProviderConnection;\n private scheduleFn?: () => Promise<void>;\n\n /**\n * Static builder method to create multiple KeycloakOrgEntityProvider instances from a single config.\n * @param deps - The dependencies required for the provider, including the configuration and logger.\n * @param options - Options for scheduling tasks and transforming users and groups.\n * @returns An array of KeycloakOrgEntityProvider instances.\n */\n static fromConfig(\n deps: {\n config: Config;\n logger: LoggerService;\n },\n options: (\n | { schedule: SchedulerServiceTaskRunner }\n | { scheduler: SchedulerService }\n ) & {\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ): KeycloakOrgEntityProvider[] {\n const { config, logger } = deps;\n return readProviderConfigs(config).map(providerConfig => {\n let taskRunner: SchedulerServiceTaskRunner | string;\n if ('scheduler' in options && providerConfig.schedule) {\n // Create a scheduled task runner using the provided scheduler and schedule configuration\n taskRunner = options.scheduler.createScheduledTaskRunner(\n providerConfig.schedule,\n );\n } else if ('schedule' in options) {\n // Use the provided schedule directly\n taskRunner = options.schedule;\n } else {\n throw new InputError(\n `No schedule provided via config for KeycloakOrgEntityProvider:${providerConfig.id}.`,\n );\n }\n\n const provider = new KeycloakOrgEntityProvider({\n id: providerConfig.id,\n provider: providerConfig,\n logger: logger,\n taskRunner: taskRunner,\n userTransformer: options.userTransformer,\n groupTransformer: options.groupTransformer,\n });\n\n return provider;\n });\n }\n\n constructor(\n private options: {\n id: string;\n provider: KeycloakProviderConfig;\n logger: LoggerService;\n taskRunner: SchedulerServiceTaskRunner;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ) {\n this.schedule(options.taskRunner);\n }\n\n /**\n * Returns the name of this entity provider.\n */\n getProviderName(): string {\n return `KeycloakOrgEntityProvider:${this.options.id}`;\n }\n\n /**\n * Connect to Backstage catalog entity provider\n * @param connection - The connection to the catalog API ingestor, which allows the provision of new entities.\n */\n async connect(connection: EntityProviderConnection) {\n this.connection = connection;\n await this.scheduleFn?.();\n }\n\n /**\n * Runs one complete ingestion loop. Call this method regularly at some\n * appropriate cadence.\n */\n async read(options?: { logger?: LoggerService }) {\n if (!this.connection) {\n throw new NotFoundError('Not initialized');\n }\n\n const logger = options?.logger ?? this.options.logger;\n const provider = this.options.provider;\n\n const { markReadComplete } = trackProgress(logger);\n const KeyCloakAdminClientModule = await import(\n '@keycloak/keycloak-admin-client'\n );\n const KeyCloakAdminClient = KeyCloakAdminClientModule.default;\n\n const kcAdminClient = new KeyCloakAdminClient({\n baseUrl: provider.baseUrl,\n realmName: provider.loginRealm,\n });\n await authenticate(kcAdminClient, provider, logger);\n\n const pLimitCJSModule = await import('p-limit');\n const limitFunc = pLimitCJSModule.default;\n const concurrency = provider.maxConcurrency ?? 20;\n const limit: LimitFunction = limitFunc(concurrency);\n\n const { users, groups } = await readKeycloakRealm(\n kcAdminClient,\n provider,\n logger,\n limit,\n {\n userQuerySize: provider.userQuerySize,\n groupQuerySize: provider.groupQuerySize,\n userTransformer: this.options.userTransformer,\n groupTransformer: this.options.groupTransformer,\n },\n );\n\n const { markCommitComplete } = markReadComplete({ users, groups });\n\n await this.connection.applyMutation({\n type: 'full',\n entities: [...users, ...groups].map(entity => ({\n locationKey: `keycloak-org-provider:${this.options.id}`,\n entity: withLocations(provider.baseUrl, provider.realm, entity),\n })),\n });\n\n markCommitComplete();\n }\n\n /**\n * Periodically schedules a task to read Keycloak user and group information, parse it, and provision it to the Backstage catalog.\n * @param taskRunner - The task runner to use for scheduling tasks.\n */\n schedule(taskRunner: SchedulerServiceTaskRunner) {\n this.scheduleFn = async () => {\n const id = `${this.getProviderName()}:refresh`;\n await taskRunner.run({\n id,\n fn: async () => {\n const logger = this.options.logger.child({\n class: KeycloakOrgEntityProvider.prototype.constructor.name,\n taskId: id,\n taskInstanceId: uuid.v4(),\n });\n\n try {\n await this.read({ logger });\n } catch (error) {\n if (isError(error)) {\n // Ensure that we don't log any sensitive internal data:\n logger.error('Error while syncing Keycloak users and groups', {\n // Default Error properties:\n name: error.name,\n cause: error.cause,\n message: error.message,\n stack: error.stack,\n // Additional status code if available:\n status: (error.response as { status?: string })?.status,\n });\n }\n }\n },\n });\n };\n }\n}\n\n// Helps wrap the timing and logging behaviors\nfunction trackProgress(logger: LoggerService) {\n let timestamp = Date.now();\n let summary: string;\n\n logger.info('Reading Keycloak users and groups');\n\n function markReadComplete(read: { users: unknown[]; groups: unknown[] }) {\n summary = `${read.users.length} Keycloak users and ${read.groups.length} Keycloak groups`;\n const readDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n timestamp = Date.now();\n logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);\n return { markCommitComplete };\n }\n\n function markCommitComplete() {\n const commitDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n logger.info(`Committed ${summary} in ${commitDuration} seconds.`);\n }\n\n return { markReadComplete };\n}\n"],"names":["KEYCLOAK_ID_ANNOTATION","merge","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION","config","readProviderConfigs","InputError","NotFoundError","authenticate","readKeycloakRealm","uuid","isError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"KeycloakOrgEntityProvider.cjs.js","sources":["../../src/providers/KeycloakOrgEntityProvider.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n LoggerService,\n SchedulerService,\n SchedulerServiceTaskRunner,\n} from '@backstage/backend-plugin-api';\nimport {\n ANNOTATION_LOCATION,\n ANNOTATION_ORIGIN_LOCATION,\n type Entity,\n} from '@backstage/catalog-model';\nimport type { Config } from '@backstage/config';\nimport { InputError, isError, NotFoundError } from '@backstage/errors';\nimport type {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\n\n// @ts-ignore\nimport { merge } from 'lodash';\nimport { LimitFunction } from 'p-limit';\nimport * as uuid from 'uuid';\n\nimport {\n GroupTransformer,\n KEYCLOAK_ID_ANNOTATION,\n KeycloakProviderConfig,\n UserTransformer,\n} from '../lib';\nimport { readProviderConfigs } from '../lib/config';\nimport { readKeycloakRealm } from '../lib/read';\nimport { authenticate } from '../lib/authenticate';\nimport { Attributes, Counter, Meter, metrics } from '@opentelemetry/api';\n\n/**\n * Options for {@link KeycloakOrgEntityProvider}.\n *\n * @public\n */\nexport interface KeycloakOrgEntityProviderOptions {\n /**\n * A unique, stable identifier for this provider.\n *\n * @example \"production\"\n */\n id: string;\n\n /**\n * The refresh schedule to use.\n * @remarks\n *\n * You can pass in the result of\n * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner}\n * to enable automatic scheduling of tasks.\n */\n schedule?: SchedulerServiceTaskRunner;\n\n /**\n * Scheduler used to schedule refreshes based on\n * the schedule config.\n */\n scheduler?: SchedulerService;\n\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * The function that transforms a user entry in LDAP to an entity.\n */\n userTransformer?: UserTransformer;\n\n /**\n * The function that transforms a group entry in LDAP to an entity.\n */\n groupTransformer?: GroupTransformer;\n}\n\n// Makes sure that emitted entities have a proper location\nexport const withLocations = (\n baseUrl: string,\n realm: string,\n entity: Entity,\n): Entity => {\n const kind = entity.kind === 'Group' ? 'groups' : 'users';\n const location = `url:${baseUrl}/admin/realms/${realm}/${kind}/${entity.metadata.annotations?.[KEYCLOAK_ID_ANNOTATION]}`;\n return merge(\n {\n metadata: {\n annotations: {\n [ANNOTATION_LOCATION]: location,\n [ANNOTATION_ORIGIN_LOCATION]: location,\n },\n },\n },\n entity,\n ) as Entity;\n};\n\n/**\n * Ingests org data (users and groups) from GitHub.\n *\n * @public\n */\nexport class KeycloakOrgEntityProvider implements EntityProvider {\n private connection?: EntityProviderConnection;\n private meter: Meter;\n private counter: Counter<Attributes>;\n private scheduleFn?: () => Promise<void>;\n\n /**\n * Static builder method to create multiple KeycloakOrgEntityProvider instances from a single config.\n * @param deps - The dependencies required for the provider, including the configuration and logger.\n * @param options - Options for scheduling tasks and transforming users and groups.\n * @returns An array of KeycloakOrgEntityProvider instances.\n */\n static fromConfig(\n deps: {\n config: Config;\n logger: LoggerService;\n },\n options: (\n | { schedule: SchedulerServiceTaskRunner }\n | { scheduler: SchedulerService }\n ) & {\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ): KeycloakOrgEntityProvider[] {\n const { config, logger } = deps;\n return readProviderConfigs(config).map(providerConfig => {\n let taskRunner: SchedulerServiceTaskRunner | string;\n if ('scheduler' in options && providerConfig.schedule) {\n // Create a scheduled task runner using the provided scheduler and schedule configuration\n taskRunner = options.scheduler.createScheduledTaskRunner(\n providerConfig.schedule,\n );\n } else if ('schedule' in options) {\n // Use the provided schedule directly\n taskRunner = options.schedule;\n } else {\n throw new InputError(\n `No schedule provided via config for KeycloakOrgEntityProvider:${providerConfig.id}.`,\n );\n }\n\n const provider = new KeycloakOrgEntityProvider({\n id: providerConfig.id,\n provider: providerConfig,\n logger: logger,\n taskRunner: taskRunner,\n userTransformer: options.userTransformer,\n groupTransformer: options.groupTransformer,\n });\n\n return provider;\n });\n }\n\n constructor(\n private options: {\n id: string;\n provider: KeycloakProviderConfig;\n logger: LoggerService;\n taskRunner: SchedulerServiceTaskRunner;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ) {\n this.meter = metrics.getMeter('default');\n this.counter = this.meter.createCounter(\n 'backend_keycloak.fetch.task.failure.count',\n {\n description:\n 'Counts the number of failed Keycloak data fetch tasks. Each increment indicates a complete failure of a fetch task, meaning no data was provided to the Catalog API. However, data may still be fetched in subsequent tasks, depending on the nature of the error.',\n },\n );\n this.schedule(options.taskRunner);\n }\n\n /**\n * Returns the name of this entity provider.\n */\n getProviderName(): string {\n return `KeycloakOrgEntityProvider:${this.options.id}`;\n }\n\n /**\n * Connect to Backstage catalog entity provider\n * @param connection - The connection to the catalog API ingestor, which allows the provision of new entities.\n */\n async connect(connection: EntityProviderConnection) {\n this.connection = connection;\n await this.scheduleFn?.();\n }\n\n /**\n * Runs one complete ingestion loop. Call this method regularly at some\n * appropriate cadence.\n */\n async read(options: { logger?: LoggerService; taskInstanceId: string }) {\n if (!this.connection) {\n throw new NotFoundError('Not initialized');\n }\n\n const logger = options?.logger ?? this.options.logger;\n const provider = this.options.provider;\n\n const { markReadComplete } = trackProgress(logger);\n const KeyCloakAdminClientModule = await import(\n '@keycloak/keycloak-admin-client'\n );\n const KeyCloakAdminClient = KeyCloakAdminClientModule.default;\n\n const kcAdminClient = new KeyCloakAdminClient({\n baseUrl: provider.baseUrl,\n realmName: provider.loginRealm,\n });\n await authenticate(kcAdminClient, provider, logger);\n\n const pLimitCJSModule = await import('p-limit');\n const limitFunc = pLimitCJSModule.default;\n const concurrency = provider.maxConcurrency ?? 20;\n const limit: LimitFunction = limitFunc(concurrency);\n\n const dataBatchFailureCounter = this.meter.createCounter(\n 'backend_keycloak.fetch.data.batch.failure.count',\n {\n description:\n 'Keycloak data batch fetch failure counter. Incremented for each batch fetch failure. Each failure means that a part of the data was not fetched due to an error, and thus the corresponding data batch was skipped during the current fetch task.',\n },\n );\n const { users, groups } = await readKeycloakRealm(\n kcAdminClient,\n provider,\n logger,\n limit,\n options.taskInstanceId,\n dataBatchFailureCounter,\n {\n userQuerySize: provider.userQuerySize,\n groupQuerySize: provider.groupQuerySize,\n userTransformer: this.options.userTransformer,\n groupTransformer: this.options.groupTransformer,\n },\n );\n\n const { markCommitComplete } = markReadComplete({ users, groups });\n\n await this.connection.applyMutation({\n type: 'full',\n entities: [...users, ...groups].map(entity => ({\n locationKey: `keycloak-org-provider:${this.options.id}`,\n entity: withLocations(provider.baseUrl, provider.realm, entity),\n })),\n });\n\n markCommitComplete();\n }\n\n /**\n * Periodically schedules a task to read Keycloak user and group information, parse it, and provision it to the Backstage catalog.\n * @param taskRunner - The task runner to use for scheduling tasks.\n */\n schedule(taskRunner: SchedulerServiceTaskRunner) {\n this.scheduleFn = async () => {\n const id = `${this.getProviderName()}:refresh`;\n await taskRunner.run({\n id,\n fn: async () => {\n const taskInstanceId = uuid.v4();\n const logger = this.options.logger.child({\n class: KeycloakOrgEntityProvider.prototype.constructor.name,\n taskId: id,\n taskInstanceId: taskInstanceId,\n });\n\n try {\n await this.read({ logger, taskInstanceId });\n } catch (error) {\n this.counter.add(1, { taskInstanceId: taskInstanceId });\n if (isError(error)) {\n // Ensure that we don't log any sensitive internal data:\n logger.error('Error while syncing Keycloak users and groups', {\n // Default Error properties:\n name: error.name,\n cause: error.cause,\n message: error.message,\n stack: error.stack,\n // Additional status code if available:\n status: (error.response as { status?: string })?.status,\n });\n }\n }\n },\n });\n };\n }\n}\n\n// Helps wrap the timing and logging behaviors\nfunction trackProgress(logger: LoggerService) {\n let timestamp = Date.now();\n let summary: string;\n\n logger.info('Reading Keycloak users and groups');\n\n function markReadComplete(read: { users: unknown[]; groups: unknown[] }) {\n summary = `${read.users.length} Keycloak users and ${read.groups.length} Keycloak groups`;\n const readDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n timestamp = Date.now();\n logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);\n return { markCommitComplete };\n }\n\n function markCommitComplete() {\n const commitDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n logger.info(`Committed ${summary} in ${commitDuration} seconds.`);\n }\n\n return { markReadComplete };\n}\n"],"names":["KEYCLOAK_ID_ANNOTATION","merge","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION","metrics","config","readProviderConfigs","InputError","NotFoundError","authenticate","readKeycloakRealm","uuid","isError"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage-community/plugin-catalog-backend-module-keycloak",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.1",
|
|
4
4
|
"description": "A Backend backend plugin for Keycloak",
|
|
5
5
|
"main": "./dist/index.cjs.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"@backstage/errors": "^1.2.7",
|
|
53
53
|
"@backstage/plugin-catalog-node": "^1.16.0",
|
|
54
54
|
"@keycloak/keycloak-admin-client": "26.1.4",
|
|
55
|
+
"@opentelemetry/api": "^1.9.0",
|
|
55
56
|
"jsonwebtoken": "^9.0.0",
|
|
56
57
|
"lodash": "^4.17.21",
|
|
57
58
|
"p-limit": "^6.1.0",
|
|
@@ -92,7 +93,6 @@
|
|
|
92
93
|
"works-with:rhbk:^26",
|
|
93
94
|
"works-with:keycloak:~26"
|
|
94
95
|
],
|
|
95
|
-
"homepage": "https://red.ht/rhdh",
|
|
96
96
|
"bugs": "https://github.com/backstage/community-plugins/issues",
|
|
97
97
|
"maintainers": [
|
|
98
98
|
"@04kash"
|