@backstage/plugin-catalog-backend-module-github 0.4.0-next.3 → 0.4.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,47 @@
1
1
  # @backstage/plugin-catalog-backend-module-github
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fa7004d9722c: Added a `catalogModuleGithubOrgEntityProvider` for the new backend system
8
+
9
+ ### Patch Changes
10
+
11
+ - 71114ac50e02: The export for the new backend system has been moved to be the `default` export.
12
+
13
+ For example, if you are currently importing the plugin using the following pattern:
14
+
15
+ ```ts
16
+ import { examplePlugin } from '@backstage/plugin-example-backend';
17
+
18
+ backend.add(examplePlugin);
19
+ ```
20
+
21
+ It should be migrated to this:
22
+
23
+ ```ts
24
+ backend.add(import('@backstage/plugin-example-backend'));
25
+ ```
26
+
27
+ - 3d63e60f3c36: Internal restructure to avoid circular imports
28
+ - 96353bb7cb4a: Properly support custom `userTransformer` returning `undefined` in `GithubMultiOrgEntityProvider`
29
+ - 3c44761b9191: Allow github user and team transforms to return any Entity
30
+ - Updated dependencies
31
+ - @backstage/plugin-catalog-backend@1.13.0
32
+ - @backstage/backend-tasks@0.5.8
33
+ - @backstage/backend-common@0.19.5
34
+ - @backstage/config@1.1.0
35
+ - @backstage/catalog-client@1.4.4
36
+ - @backstage/catalog-model@1.4.2
37
+ - @backstage/errors@1.2.2
38
+ - @backstage/integration@1.7.0
39
+ - @backstage/plugin-catalog-common@1.0.16
40
+ - @backstage/types@1.1.1
41
+ - @backstage/backend-plugin-api@0.6.3
42
+ - @backstage/plugin-catalog-node@1.4.4
43
+ - @backstage/plugin-events-node@0.2.12
44
+
3
45
  ## 0.4.0-next.3
4
46
 
5
47
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend-module-github",
3
- "version": "0.4.0-next.3",
3
+ "version": "0.4.0",
4
4
  "main": "../dist/alpha.cjs.js",
5
5
  "types": "../dist/alpha.d.ts"
6
6
  }
package/dist/alpha.cjs.js CHANGED
@@ -5,7 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var backendPluginApi = require('@backstage/backend-plugin-api');
6
6
  var backendCommon = require('@backstage/backend-common');
7
7
  var alpha = require('@backstage/plugin-catalog-node/alpha');
8
- var GithubEntityProvider = require('./cjs/GithubEntityProvider-9bc718e3.cjs.js');
8
+ var GithubEntityProvider = require('./cjs/GithubEntityProvider-656c132f.cjs.js');
9
9
  var backendTasks = require('@backstage/backend-tasks');
10
10
  var pluginCatalogBackendModuleGithub = require('@backstage/plugin-catalog-backend-module-github');
11
11
  require('@backstage/integration');
@@ -93,56 +93,6 @@ const defaultOrganizationTeamTransformer = async (team) => {
93
93
  return entity;
94
94
  };
95
95
 
96
- function buildOrgHierarchy(groups) {
97
- const groupsByName = new Map(groups.map((g) => [g.metadata.name, g]));
98
- for (const group of groups) {
99
- const selfName = group.metadata.name;
100
- const parentName = group.spec.parent;
101
- if (parentName) {
102
- const parent = groupsByName.get(parentName);
103
- if (parent && !parent.spec.children.includes(selfName)) {
104
- parent.spec.children.push(selfName);
105
- }
106
- }
107
- }
108
- for (const group of groups) {
109
- const selfName = group.metadata.name;
110
- for (const childName of group.spec.children) {
111
- const child = groupsByName.get(childName);
112
- if (child && !child.spec.parent) {
113
- child.spec.parent = selfName;
114
- }
115
- }
116
- }
117
- }
118
- function assignGroupsToUsers(users, groups) {
119
- var _a;
120
- const groupMemberUsers = new Map(
121
- groups.map((group) => {
122
- var _a2;
123
- const groupKey = group.metadata.namespace && group.metadata.namespace !== catalogModel.DEFAULT_NAMESPACE ? `${group.metadata.namespace}/${group.metadata.name}` : group.metadata.name;
124
- return [
125
- groupKey,
126
- ((_a2 = group.spec.members) == null ? void 0 : _a2.map(
127
- (m) => catalogModel.stringifyEntityRef(catalogModel.parseEntityRef(m, { defaultKind: "user" }))
128
- )) || []
129
- ];
130
- })
131
- );
132
- const usersByRef = new Map(users.map((u) => [catalogModel.stringifyEntityRef(u), u]));
133
- for (const [groupName, userRefs] of groupMemberUsers.entries()) {
134
- for (const ref of userRefs) {
135
- const user = usersByRef.get(ref);
136
- if (user && !((_a = user.spec.memberOf) == null ? void 0 : _a.includes(groupName))) {
137
- if (!user.spec.memberOf) {
138
- user.spec.memberOf = [];
139
- }
140
- user.spec.memberOf.push(groupName);
141
- }
142
- }
143
- }
144
- }
145
-
146
96
  function parseGithubOrgUrl(urlString) {
147
97
  const path = new URL(urlString).pathname.slice(1).split("/");
148
98
  if (path.length === 1 && path[0].length) {
@@ -212,395 +162,6 @@ function satisfiesVisibilityFilter(visibilities, visibility) {
212
162
  return lowerCaseVisibilities.includes(lowerCaseVisibility);
213
163
  }
214
164
 
215
- function areGroupEntities(entities) {
216
- return entities.every((e) => catalogModel.isGroupEntity(e));
217
- }
218
- function areUserEntities(entities) {
219
- return entities.every((e) => catalogModel.isUserEntity(e));
220
- }
221
-
222
- var __defProp$1 = Object.defineProperty;
223
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
224
- var __publicField$1 = (obj, key, value) => {
225
- __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
226
- return value;
227
- };
228
- class GithubOrgEntityProvider {
229
- constructor(options) {
230
- this.options = options;
231
- __publicField$1(this, "credentialsProvider");
232
- __publicField$1(this, "connection");
233
- __publicField$1(this, "scheduleFn");
234
- this.credentialsProvider = options.githubCredentialsProvider || integration.SingleInstanceGithubCredentialsProvider.create(this.options.gitHubConfig);
235
- }
236
- static fromConfig(config, options) {
237
- var _a;
238
- const integrations = integration.ScmIntegrations.fromConfig(config);
239
- const gitHubConfig = (_a = integrations.github.byUrl(options.orgUrl)) == null ? void 0 : _a.config;
240
- if (!gitHubConfig) {
241
- throw new Error(
242
- `There is no GitHub Org provider that matches ${options.orgUrl}. Please add a configuration for an integration.`
243
- );
244
- }
245
- const logger = options.logger.child({
246
- target: options.orgUrl
247
- });
248
- const provider = new GithubOrgEntityProvider({
249
- id: options.id,
250
- orgUrl: options.orgUrl,
251
- logger,
252
- gitHubConfig,
253
- githubCredentialsProvider: options.githubCredentialsProvider || integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations),
254
- userTransformer: options.userTransformer,
255
- teamTransformer: options.teamTransformer
256
- });
257
- provider.schedule(options.schedule);
258
- return provider;
259
- }
260
- /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */
261
- getProviderName() {
262
- return `GithubOrgEntityProvider:${this.options.id}`;
263
- }
264
- /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */
265
- async connect(connection) {
266
- var _a;
267
- this.connection = connection;
268
- await ((_a = this.scheduleFn) == null ? void 0 : _a.call(this));
269
- }
270
- /**
271
- * Runs one single complete ingestion. This is only necessary if you use
272
- * manual scheduling.
273
- */
274
- async read(options) {
275
- var _a;
276
- if (!this.connection) {
277
- throw new Error("Not initialized");
278
- }
279
- const logger = (_a = options == null ? void 0 : options.logger) != null ? _a : this.options.logger;
280
- const { markReadComplete } = trackProgress(logger);
281
- const { headers, type: tokenType } = await this.credentialsProvider.getCredentials({
282
- url: this.options.orgUrl
283
- });
284
- const client = graphql.graphql.defaults({
285
- baseUrl: this.options.gitHubConfig.apiBaseUrl,
286
- headers
287
- });
288
- const { org } = parseGithubOrgUrl(this.options.orgUrl);
289
- const { users } = await getOrganizationUsers(
290
- client,
291
- org,
292
- tokenType,
293
- this.options.userTransformer
294
- );
295
- const { teams } = await getOrganizationTeams(
296
- client,
297
- org,
298
- this.options.teamTransformer
299
- );
300
- if (areGroupEntities(teams)) {
301
- buildOrgHierarchy(teams);
302
- if (areUserEntities(users)) {
303
- assignGroupsToUsers(users, teams);
304
- }
305
- }
306
- const { markCommitComplete } = markReadComplete({ users, teams });
307
- await this.connection.applyMutation({
308
- type: "full",
309
- entities: [...users, ...teams].map((entity) => ({
310
- locationKey: `github-org-provider:${this.options.id}`,
311
- entity: withLocations(
312
- `https://${this.options.gitHubConfig.host}`,
313
- org,
314
- entity
315
- )
316
- }))
317
- });
318
- markCommitComplete();
319
- }
320
- /** {@inheritdoc @backstage/plugin-events-node#EventSubscriber.onEvent} */
321
- async onEvent(params) {
322
- const { logger } = this.options;
323
- logger.debug(`Received event from ${params.topic}`);
324
- const addEntitiesOperation = createAddEntitiesOperation(
325
- this.options.id,
326
- this.options.gitHubConfig.host
327
- );
328
- const removeEntitiesOperation = createRemoveEntitiesOperation(
329
- this.options.id,
330
- this.options.gitHubConfig.host
331
- );
332
- const replaceEntitiesOperation = createReplaceEntitiesOperation(
333
- this.options.id,
334
- this.options.gitHubConfig.host
335
- );
336
- if (params.topic.includes("organization")) {
337
- const orgEvent = params.eventPayload;
338
- if (orgEvent.action === "member_added" || orgEvent.action === "member_removed") {
339
- const createDeltaOperation = orgEvent.action === "member_added" ? addEntitiesOperation : removeEntitiesOperation;
340
- await this.onMemberChangeInOrganization(orgEvent, createDeltaOperation);
341
- }
342
- }
343
- if (params.topic.includes("team")) {
344
- const teamEvent = params.eventPayload;
345
- if (teamEvent.action === "created" || teamEvent.action === "deleted") {
346
- const createDeltaOperation = teamEvent.action === "created" ? addEntitiesOperation : removeEntitiesOperation;
347
- await this.onTeamChangeInOrganization(teamEvent, createDeltaOperation);
348
- } else if (teamEvent.action === "edited") {
349
- await this.onTeamEditedInOrganization(
350
- teamEvent,
351
- replaceEntitiesOperation
352
- );
353
- }
354
- }
355
- if (params.topic.includes("membership")) {
356
- const membershipEvent = params.eventPayload;
357
- this.onMembershipChangedInOrganization(
358
- membershipEvent,
359
- replaceEntitiesOperation
360
- );
361
- }
362
- return;
363
- }
364
- /** {@inheritdoc @backstage/plugin-events-node#EventSubscriber.supportsEventTopics} */
365
- supportsEventTopics() {
366
- return ["github.organization", "github.team", "github.membership"];
367
- }
368
- async onTeamEditedInOrganization(event, createDeltaOperation) {
369
- var _a, _b;
370
- if (!this.connection) {
371
- throw new Error("Not initialized");
372
- }
373
- const teamSlug = event.team.slug;
374
- const { headers, type: tokenType } = await this.credentialsProvider.getCredentials({
375
- url: this.options.orgUrl
376
- });
377
- const client = graphql.graphql.defaults({
378
- baseUrl: this.options.gitHubConfig.apiBaseUrl,
379
- headers
380
- });
381
- const { org } = parseGithubOrgUrl(this.options.orgUrl);
382
- const { team } = await getOrganizationTeam(
383
- client,
384
- org,
385
- teamSlug,
386
- this.options.teamTransformer
387
- );
388
- const { users } = await getOrganizationUsers(
389
- client,
390
- org,
391
- tokenType,
392
- this.options.userTransformer
393
- );
394
- if (!catalogModel.isGroupEntity(team)) {
395
- return;
396
- }
397
- const usersFromChangedGroup = team.spec.members || [];
398
- const usersToRebuild = users.filter(
399
- (u) => usersFromChangedGroup.includes(u.metadata.name)
400
- );
401
- const { teams } = await getOrganizationTeamsFromUsers(
402
- client,
403
- org,
404
- usersToRebuild.map((u) => u.metadata.name),
405
- this.options.teamTransformer
406
- );
407
- if (areGroupEntities(teams)) {
408
- buildOrgHierarchy(teams);
409
- if (areUserEntities(usersToRebuild)) {
410
- assignGroupsToUsers(usersToRebuild, teams);
411
- }
412
- }
413
- const oldName = ((_a = event.changes.name) == null ? void 0 : _a.from) || event.team.name;
414
- const oldSlug = oldName.toLowerCase().replaceAll(/\s/gi, "-");
415
- const oldDescription = ((_b = event.changes.description) == null ? void 0 : _b.from) || event.team.description;
416
- const oldDescriptionSlug = oldDescription == null ? void 0 : oldDescription.toLowerCase().replaceAll(/\s/gi, "-");
417
- const { removed } = createDeltaOperation(org, [
418
- {
419
- ...team,
420
- metadata: {
421
- name: oldSlug,
422
- description: oldDescriptionSlug
423
- }
424
- }
425
- ]);
426
- const { added } = createDeltaOperation(org, [...usersToRebuild, ...teams]);
427
- await this.connection.applyMutation({
428
- type: "delta",
429
- removed,
430
- added
431
- });
432
- }
433
- async onMembershipChangedInOrganization(event, createDeltaOperation) {
434
- if (!this.connection) {
435
- throw new Error("Not initialized");
436
- }
437
- if (!("slug" in event.team)) {
438
- return;
439
- }
440
- const teamSlug = event.team.slug;
441
- const userLogin = event.member.login;
442
- const { headers, type: tokenType } = await this.credentialsProvider.getCredentials({
443
- url: this.options.orgUrl
444
- });
445
- const client = graphql.graphql.defaults({
446
- baseUrl: this.options.gitHubConfig.apiBaseUrl,
447
- headers
448
- });
449
- const { org } = parseGithubOrgUrl(this.options.orgUrl);
450
- const { team } = await getOrganizationTeam(
451
- client,
452
- org,
453
- teamSlug,
454
- this.options.teamTransformer
455
- );
456
- const { users } = await getOrganizationUsers(
457
- client,
458
- org,
459
- tokenType,
460
- this.options.userTransformer
461
- );
462
- const usersToRebuild = users.filter((u) => u.metadata.name === userLogin);
463
- const { teams } = await getOrganizationTeamsFromUsers(
464
- client,
465
- org,
466
- [userLogin],
467
- this.options.teamTransformer
468
- );
469
- if (!teams.some((t) => t.metadata.name === team.metadata.name)) {
470
- teams.push(team);
471
- }
472
- if (areGroupEntities(teams)) {
473
- buildOrgHierarchy(teams);
474
- if (areUserEntities(usersToRebuild)) {
475
- assignGroupsToUsers(usersToRebuild, teams);
476
- }
477
- }
478
- const { added, removed } = createDeltaOperation(org, [
479
- ...usersToRebuild,
480
- ...teams
481
- ]);
482
- await this.connection.applyMutation({
483
- type: "delta",
484
- removed,
485
- added
486
- });
487
- }
488
- async onTeamChangeInOrganization(event, createDeltaOperation) {
489
- var _a, _b;
490
- if (!this.connection) {
491
- throw new Error("Not initialized");
492
- }
493
- const organizationTeamTransformer = this.options.teamTransformer || defaultOrganizationTeamTransformer;
494
- const { name, html_url: url, description, slug } = event.team;
495
- const org = event.organization.login;
496
- const { headers } = await this.credentialsProvider.getCredentials({
497
- url: this.options.orgUrl
498
- });
499
- const client = graphql.graphql.defaults({
500
- baseUrl: this.options.gitHubConfig.apiBaseUrl,
501
- headers
502
- });
503
- const group = await organizationTeamTransformer(
504
- {
505
- name,
506
- slug,
507
- editTeamUrl: `${url}/edit`,
508
- combinedSlug: `${org}/${slug}`,
509
- description: description || void 0,
510
- parentTeam: { slug: ((_b = (_a = event.team) == null ? void 0 : _a.parent) == null ? void 0 : _b.slug) || "" },
511
- // entity will be removed
512
- members: []
513
- },
514
- {
515
- org,
516
- client,
517
- query: ""
518
- }
519
- );
520
- const { added, removed } = createDeltaOperation(org, [group]);
521
- await this.connection.applyMutation({
522
- type: "delta",
523
- removed,
524
- added
525
- });
526
- }
527
- async onMemberChangeInOrganization(event, createDeltaOperation) {
528
- if (!this.connection) {
529
- throw new Error("Not initialized");
530
- }
531
- const userTransformer = this.options.userTransformer || defaultUserTransformer;
532
- const { name, avatar_url: avatarUrl, email, login } = event.membership.user;
533
- const org = event.organization.login;
534
- const { headers } = await this.credentialsProvider.getCredentials({
535
- url: this.options.orgUrl
536
- });
537
- const client = graphql.graphql.defaults({
538
- baseUrl: this.options.gitHubConfig.apiBaseUrl,
539
- headers
540
- });
541
- const user = await userTransformer(
542
- {
543
- name,
544
- avatarUrl,
545
- login,
546
- email: email || void 0,
547
- // we don't have this information in the event, so the refresh will handle that for us
548
- organizationVerifiedDomainEmails: []
549
- },
550
- {
551
- org,
552
- client,
553
- query: ""
554
- }
555
- );
556
- const { added, removed } = createDeltaOperation(org, [user]);
557
- await this.connection.applyMutation({
558
- type: "delta",
559
- removed,
560
- added
561
- });
562
- }
563
- schedule(schedule) {
564
- if (!schedule || schedule === "manual") {
565
- return;
566
- }
567
- this.scheduleFn = async () => {
568
- const id = `${this.getProviderName()}:refresh`;
569
- await schedule.run({
570
- id,
571
- fn: async () => {
572
- const logger = this.options.logger.child({
573
- class: GithubOrgEntityProvider.prototype.constructor.name,
574
- taskId: id,
575
- taskInstanceId: uuid__namespace.v4()
576
- });
577
- try {
578
- await this.read({ logger });
579
- } catch (error) {
580
- logger.error(`${this.getProviderName()} refresh failed`, error);
581
- }
582
- }
583
- });
584
- };
585
- }
586
- }
587
- function trackProgress(logger) {
588
- let timestamp = Date.now();
589
- let summary;
590
- logger.info("Reading GitHub users and teams");
591
- function markReadComplete(read) {
592
- summary = `${read.users.length} GitHub users and ${read.teams.length} GitHub teams`;
593
- const readDuration = ((Date.now() - timestamp) / 1e3).toFixed(1);
594
- timestamp = Date.now();
595
- logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);
596
- return { markCommitComplete };
597
- }
598
- function markCommitComplete() {
599
- const commitDuration = ((Date.now() - timestamp) / 1e3).toFixed(1);
600
- logger.info(`Committed ${summary} in ${commitDuration} seconds.`);
601
- }
602
- return { markReadComplete };
603
- }
604
165
  function withLocations(baseUrl, org, entity) {
605
166
  var _a, _b;
606
167
  const login = ((_a = entity.metadata.annotations) == null ? void 0 : _a[ANNOTATION_GITHUB_USER_LOGIN]) || entity.metadata.name;
@@ -1349,11 +910,9 @@ class GithubEntityProvider {
1349
910
  exports.ANNOTATION_GITHUB_TEAM_SLUG = ANNOTATION_GITHUB_TEAM_SLUG;
1350
911
  exports.ANNOTATION_GITHUB_USER_LOGIN = ANNOTATION_GITHUB_USER_LOGIN;
1351
912
  exports.GithubEntityProvider = GithubEntityProvider;
1352
- exports.GithubOrgEntityProvider = GithubOrgEntityProvider;
1353
- exports.areGroupEntities = areGroupEntities;
1354
- exports.areUserEntities = areUserEntities;
1355
- exports.assignGroupsToUsers = assignGroupsToUsers;
1356
- exports.buildOrgHierarchy = buildOrgHierarchy;
913
+ exports.createAddEntitiesOperation = createAddEntitiesOperation;
914
+ exports.createRemoveEntitiesOperation = createRemoveEntitiesOperation;
915
+ exports.createReplaceEntitiesOperation = createReplaceEntitiesOperation;
1357
916
  exports.defaultOrganizationTeamTransformer = defaultOrganizationTeamTransformer;
1358
917
  exports.defaultUserTransformer = defaultUserTransformer;
1359
918
  exports.getOrganizationRepositories = getOrganizationRepositories;
@@ -1364,4 +923,5 @@ exports.getOrganizationUsers = getOrganizationUsers;
1364
923
  exports.getOrganizationsFromUser = getOrganizationsFromUser;
1365
924
  exports.parseGithubOrgUrl = parseGithubOrgUrl;
1366
925
  exports.splitTeamSlug = splitTeamSlug;
1367
- //# sourceMappingURL=GithubEntityProvider-9bc718e3.cjs.js.map
926
+ exports.withLocations = withLocations;
927
+ //# sourceMappingURL=GithubEntityProvider-656c132f.cjs.js.map