@backstage/plugin-catalog-backend-module-github 0.2.7-next.0 → 0.2.7-next.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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # @backstage/plugin-catalog-backend-module-github
2
2
 
3
+ ## 0.2.7-next.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @backstage/plugin-catalog-backend@1.8.1-next.1
9
+ - @backstage/backend-tasks@0.5.1-next.1
10
+ - @backstage/integration@1.4.4-next.0
11
+ - @backstage/backend-common@0.18.4-next.1
12
+ - @backstage/backend-plugin-api@0.5.1-next.1
13
+ - @backstage/catalog-client@1.4.0
14
+ - @backstage/catalog-model@1.2.1
15
+ - @backstage/config@1.0.7
16
+ - @backstage/errors@1.1.5
17
+ - @backstage/types@1.0.2
18
+ - @backstage/plugin-catalog-common@1.0.13-next.0
19
+ - @backstage/plugin-catalog-node@1.3.5-next.1
20
+ - @backstage/plugin-events-node@0.2.5-next.1
21
+
3
22
  ## 0.2.7-next.0
4
23
 
5
24
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend-module-github",
3
- "version": "0.2.7-next.0",
3
+ "version": "0.2.7-next.1",
4
4
  "main": "../dist/alpha.cjs.js",
5
5
  "types": "../dist/alpha.d.ts"
6
6
  }
package/dist/alpha.cjs.js CHANGED
@@ -5,488 +5,15 @@ 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 integration = require('@backstage/integration');
9
- var pluginCatalogNode = require('@backstage/plugin-catalog-node');
10
- var graphql = require('@octokit/graphql');
11
- var uuid = require('uuid');
12
- var backendTasks = require('@backstage/backend-tasks');
8
+ var GithubEntityProvider = require('./cjs/GithubEntityProvider-15bc2fa6.cjs.js');
9
+ require('@backstage/integration');
10
+ require('@backstage/plugin-catalog-node');
11
+ require('@octokit/graphql');
12
+ require('uuid');
13
+ require('@backstage/backend-tasks');
13
14
  require('@backstage/catalog-model');
14
15
  require('lodash');
15
- var minimatch = require('minimatch');
16
-
17
- function _interopNamespace(e) {
18
- if (e && e.__esModule) return e;
19
- var n = Object.create(null);
20
- if (e) {
21
- Object.keys(e).forEach(function (k) {
22
- if (k !== 'default') {
23
- var d = Object.getOwnPropertyDescriptor(e, k);
24
- Object.defineProperty(n, k, d.get ? d : {
25
- enumerable: true,
26
- get: function () { return e[k]; }
27
- });
28
- }
29
- });
30
- }
31
- n["default"] = e;
32
- return Object.freeze(n);
33
- }
34
-
35
- var uuid__namespace = /*#__PURE__*/_interopNamespace(uuid);
36
-
37
- const DEFAULT_CATALOG_PATH = "/catalog-info.yaml";
38
- const DEFAULT_PROVIDER_ID = "default";
39
- function readProviderConfigs(config) {
40
- const providersConfig = config.getOptionalConfig("catalog.providers.github");
41
- if (!providersConfig) {
42
- return [];
43
- }
44
- if (providersConfig.has("organization")) {
45
- return [readProviderConfig(DEFAULT_PROVIDER_ID, providersConfig)];
46
- }
47
- return providersConfig.keys().map((id) => {
48
- const providerConfig = providersConfig.getConfig(id);
49
- return readProviderConfig(id, providerConfig);
50
- });
51
- }
52
- function readProviderConfig(id, config) {
53
- var _a, _b, _c, _d;
54
- const organization = config.getString("organization");
55
- const catalogPath = (_a = config.getOptionalString("catalogPath")) != null ? _a : DEFAULT_CATALOG_PATH;
56
- const host = (_b = config.getOptionalString("host")) != null ? _b : "github.com";
57
- const repositoryPattern = config.getOptionalString("filters.repository");
58
- const branchPattern = config.getOptionalString("filters.branch");
59
- const allowForks = (_c = config.getOptionalBoolean("filters.allowForks")) != null ? _c : true;
60
- const topicFilterInclude = config == null ? void 0 : config.getOptionalStringArray(
61
- "filters.topic.include"
62
- );
63
- const topicFilterExclude = config == null ? void 0 : config.getOptionalStringArray(
64
- "filters.topic.exclude"
65
- );
66
- const validateLocationsExist = (_d = config == null ? void 0 : config.getOptionalBoolean("validateLocationsExist")) != null ? _d : false;
67
- const catalogPathContainsWildcard = catalogPath.includes("*");
68
- if (validateLocationsExist && catalogPathContainsWildcard) {
69
- throw Error(
70
- `Error while processing GitHub provider config. The catalog path ${catalogPath} contains a wildcard, which is incompatible with validation of locations existing before emitting them. Ensure that validateLocationsExist is set to false.`
71
- );
72
- }
73
- const schedule = config.has("schedule") ? backendTasks.readTaskScheduleDefinitionFromConfig(config.getConfig("schedule")) : void 0;
74
- return {
75
- id,
76
- catalogPath,
77
- organization,
78
- host,
79
- filters: {
80
- repository: repositoryPattern ? compileRegExp(repositoryPattern) : void 0,
81
- branch: branchPattern || void 0,
82
- allowForks,
83
- topic: {
84
- include: topicFilterInclude,
85
- exclude: topicFilterExclude
86
- }
87
- },
88
- schedule,
89
- validateLocationsExist
90
- };
91
- }
92
- function compileRegExp(pattern) {
93
- let fullLinePattern = pattern;
94
- if (!fullLinePattern.startsWith("^")) {
95
- fullLinePattern = `^${fullLinePattern}`;
96
- }
97
- if (!fullLinePattern.endsWith("$")) {
98
- fullLinePattern = `${fullLinePattern}$`;
99
- }
100
- return new RegExp(fullLinePattern);
101
- }
102
-
103
- function satisfiesTopicFilter(topics, topicFilter) {
104
- var _a, _b, _c, _d;
105
- if (!topicFilter)
106
- return true;
107
- if (!topicFilter.include && !topicFilter.exclude)
108
- return true;
109
- if (!((_a = topicFilter.include) == null ? void 0 : _a.length) && !((_b = topicFilter.exclude) == null ? void 0 : _b.length))
110
- return true;
111
- if (((_c = topicFilter.include) == null ? void 0 : _c.length) && !topicFilter.exclude) {
112
- for (const topic of topics) {
113
- if (topicFilter.include.includes(topic))
114
- return true;
115
- }
116
- return false;
117
- }
118
- if (!topicFilter.include && ((_d = topicFilter.exclude) == null ? void 0 : _d.length)) {
119
- if (!topics.length)
120
- return true;
121
- for (const topic of topics) {
122
- if (topicFilter.exclude.includes(topic))
123
- return false;
124
- }
125
- return true;
126
- }
127
- if (topicFilter.include && topicFilter.exclude) {
128
- const matchesInclude = satisfiesTopicFilter(topics, {
129
- include: topicFilter.include
130
- });
131
- const matchesExclude = !satisfiesTopicFilter(topics, {
132
- exclude: topicFilter.exclude
133
- });
134
- if (matchesExclude)
135
- return false;
136
- return matchesInclude;
137
- }
138
- return true;
139
- }
140
- function satisfiesForkFilter(allowForks, isFork) {
141
- if (!allowForks && isFork)
142
- return false;
143
- return true;
144
- }
145
-
146
- async function getOrganizationRepositories(client, org, catalogPath) {
147
- let relativeCatalogPathRef;
148
- if (catalogPath.startsWith("/")) {
149
- relativeCatalogPathRef = catalogPath.substring(1);
150
- } else {
151
- relativeCatalogPathRef = catalogPath;
152
- }
153
- const catalogPathRef = `HEAD:${relativeCatalogPathRef}`;
154
- const query = `
155
- query repositories($org: String!, $catalogPathRef: String!, $cursor: String) {
156
- repositoryOwner(login: $org) {
157
- login
158
- repositories(first: 100, after: $cursor) {
159
- nodes {
160
- name
161
- catalogInfoFile: object(expression: $catalogPathRef) {
162
- __typename
163
- ... on Blob {
164
- id
165
- text
166
- }
167
- }
168
- url
169
- isArchived
170
- isFork
171
- repositoryTopics(first: 100) {
172
- nodes {
173
- ... on RepositoryTopic {
174
- topic {
175
- name
176
- }
177
- }
178
- }
179
- }
180
- defaultBranchRef {
181
- name
182
- }
183
- }
184
- pageInfo {
185
- hasNextPage
186
- endCursor
187
- }
188
- }
189
- }
190
- }`;
191
- const repositories = await queryWithPaging(
192
- client,
193
- query,
194
- org,
195
- (r) => {
196
- var _a;
197
- return (_a = r.repositoryOwner) == null ? void 0 : _a.repositories;
198
- },
199
- async (x) => x,
200
- { org, catalogPathRef }
201
- );
202
- return { repositories };
203
- }
204
- async function queryWithPaging(client, query, org, connection, transformer, variables) {
205
- const result = [];
206
- let cursor = void 0;
207
- for (let j = 0; j < 1e3; ++j) {
208
- const response = await client(query, {
209
- ...variables,
210
- cursor
211
- });
212
- const conn = connection(response);
213
- if (!conn) {
214
- throw new Error(`Found no match for ${JSON.stringify(variables)}`);
215
- }
216
- for (const node of conn.nodes) {
217
- const transformedNode = await transformer(node, {
218
- client,
219
- query,
220
- org
221
- });
222
- if (transformedNode) {
223
- result.push(transformedNode);
224
- }
225
- }
226
- if (!conn.pageInfo.hasNextPage) {
227
- break;
228
- } else {
229
- cursor = conn.pageInfo.endCursor;
230
- }
231
- }
232
- return result;
233
- }
234
-
235
- const TOPIC_REPO_PUSH = "github.push";
236
- class GithubEntityProvider {
237
- static fromConfig(config, options) {
238
- if (!options.schedule && !options.scheduler) {
239
- throw new Error("Either schedule or scheduler must be provided.");
240
- }
241
- const integrations = integration.ScmIntegrations.fromConfig(config);
242
- return readProviderConfigs(config).map((providerConfig) => {
243
- var _a;
244
- const integrationHost = providerConfig.host;
245
- const integration = integrations.github.byHost(integrationHost);
246
- if (!integration) {
247
- throw new Error(
248
- `There is no GitHub config that matches host ${integrationHost}. Please add a configuration entry for it under integrations.github`
249
- );
250
- }
251
- if (!options.schedule && !providerConfig.schedule) {
252
- throw new Error(
253
- `No schedule provided neither via code nor config for github-provider:${providerConfig.id}.`
254
- );
255
- }
256
- const taskRunner = (_a = options.schedule) != null ? _a : options.scheduler.createScheduledTaskRunner(providerConfig.schedule);
257
- return new GithubEntityProvider(
258
- providerConfig,
259
- integration,
260
- options.logger,
261
- taskRunner
262
- );
263
- });
264
- }
265
- constructor(config, integration$1, logger, taskRunner) {
266
- this.config = config;
267
- this.integration = integration$1.config;
268
- this.logger = logger.child({
269
- target: this.getProviderName()
270
- });
271
- this.scheduleFn = this.createScheduleFn(taskRunner);
272
- this.githubCredentialsProvider = integration.SingleInstanceGithubCredentialsProvider.create(integration$1.config);
273
- }
274
- /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */
275
- getProviderName() {
276
- return `github-provider:${this.config.id}`;
277
- }
278
- /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */
279
- async connect(connection) {
280
- this.connection = connection;
281
- return await this.scheduleFn();
282
- }
283
- createScheduleFn(taskRunner) {
284
- return async () => {
285
- const taskId = `${this.getProviderName()}:refresh`;
286
- return taskRunner.run({
287
- id: taskId,
288
- fn: async () => {
289
- const logger = this.logger.child({
290
- class: GithubEntityProvider.prototype.constructor.name,
291
- taskId,
292
- taskInstanceId: uuid__namespace.v4()
293
- });
294
- try {
295
- await this.refresh(logger);
296
- } catch (error) {
297
- logger.error(`${this.getProviderName()} refresh failed`, error);
298
- }
299
- }
300
- });
301
- };
302
- }
303
- async refresh(logger) {
304
- if (!this.connection) {
305
- throw new Error("Not initialized");
306
- }
307
- const targets = await this.findCatalogFiles();
308
- const matchingTargets = this.matchesFilters(targets);
309
- const entities = matchingTargets.map((repository) => this.createLocationUrl(repository)).map(GithubEntityProvider.toLocationSpec).map((location) => {
310
- return {
311
- locationKey: this.getProviderName(),
312
- entity: pluginCatalogNode.locationSpecToLocationEntity({ location })
313
- };
314
- });
315
- await this.connection.applyMutation({
316
- type: "full",
317
- entities
318
- });
319
- logger.info(
320
- `Read ${targets.length} GitHub repositories (${entities.length} matching the pattern)`
321
- );
322
- }
323
- // go to the server and get all of the repositories
324
- async findCatalogFiles() {
325
- const organization = this.config.organization;
326
- const host = this.integration.host;
327
- const catalogPath = this.config.catalogPath;
328
- const orgUrl = `https://${host}/${organization}`;
329
- const { headers } = await this.githubCredentialsProvider.getCredentials({
330
- url: orgUrl
331
- });
332
- const client = graphql.graphql.defaults({
333
- baseUrl: this.integration.apiBaseUrl,
334
- headers
335
- });
336
- const { repositories: repositoriesFromGithub } = await getOrganizationRepositories(client, organization, catalogPath);
337
- const repositories = repositoriesFromGithub.map((r) => {
338
- var _a, _b;
339
- return {
340
- url: r.url,
341
- name: r.name,
342
- defaultBranchRef: (_a = r.defaultBranchRef) == null ? void 0 : _a.name,
343
- repositoryTopics: r.repositoryTopics.nodes.map((t) => t.topic.name),
344
- isArchived: r.isArchived,
345
- isFork: r.isFork,
346
- isCatalogInfoFilePresent: ((_b = r.catalogInfoFile) == null ? void 0 : _b.__typename) === "Blob" && r.catalogInfoFile.text !== ""
347
- };
348
- });
349
- if (this.config.validateLocationsExist) {
350
- return repositories.filter(
351
- (repository) => repository.isCatalogInfoFilePresent
352
- );
353
- }
354
- return repositories;
355
- }
356
- matchesFilters(repositories) {
357
- var _a, _b, _c, _d;
358
- const repositoryFilter = (_a = this.config.filters) == null ? void 0 : _a.repository;
359
- const topicFilters = (_b = this.config.filters) == null ? void 0 : _b.topic;
360
- const allowForks = (_d = (_c = this.config.filters) == null ? void 0 : _c.allowForks) != null ? _d : true;
361
- const matchingRepositories = repositories.filter((r) => {
362
- const repoTopics = r.repositoryTopics;
363
- return !r.isArchived && (!repositoryFilter || repositoryFilter.test(r.name)) && satisfiesTopicFilter(repoTopics, topicFilters) && satisfiesForkFilter(allowForks, r.isFork) && r.defaultBranchRef;
364
- });
365
- return matchingRepositories;
366
- }
367
- createLocationUrl(repository) {
368
- var _a;
369
- const branch = ((_a = this.config.filters) == null ? void 0 : _a.branch) || repository.defaultBranchRef || "-";
370
- const catalogFile = this.config.catalogPath.startsWith("/") ? this.config.catalogPath.substring(1) : this.config.catalogPath;
371
- return `${repository.url}/blob/${branch}/${catalogFile}`;
372
- }
373
- static toLocationSpec(target) {
374
- return {
375
- type: "url",
376
- target,
377
- presence: "optional"
378
- };
379
- }
380
- /** {@inheritdoc @backstage/plugin-events-node#EventSubscriber.onEvent} */
381
- async onEvent(params) {
382
- this.logger.debug(`Received event from ${params.topic}`);
383
- if (params.topic !== TOPIC_REPO_PUSH) {
384
- return;
385
- }
386
- await this.onRepoPush(params.eventPayload);
387
- }
388
- /** {@inheritdoc @backstage/plugin-events-node#EventSubscriber.supportsEventTopics} */
389
- supportsEventTopics() {
390
- return [TOPIC_REPO_PUSH];
391
- }
392
- async onRepoPush(event) {
393
- var _a;
394
- if (!this.connection) {
395
- throw new Error("Not initialized");
396
- }
397
- const repoName = event.repository.name;
398
- const repoUrl = event.repository.url;
399
- this.logger.debug(`handle github:push event for ${repoName} - ${repoUrl}`);
400
- const branch = ((_a = this.config.filters) == null ? void 0 : _a.branch) || event.repository.default_branch;
401
- if (!event.ref.includes(branch)) {
402
- this.logger.debug(`skipping push event from ref ${event.ref}`);
403
- return;
404
- }
405
- const repository = {
406
- url: event.repository.url,
407
- name: event.repository.name,
408
- defaultBranchRef: event.repository.default_branch,
409
- repositoryTopics: event.repository.topics,
410
- isArchived: event.repository.archived,
411
- isFork: event.repository.fork,
412
- // we can consider this file present because
413
- // only the catalog file will be recovered from the commits
414
- isCatalogInfoFilePresent: true
415
- };
416
- const matchingTargets = this.matchesFilters([repository]);
417
- if (matchingTargets.length === 0) {
418
- this.logger.debug(
419
- `skipping push event from repository ${repoName} because didn't match provider filters`
420
- );
421
- return;
422
- }
423
- const added = this.collectDeferredEntitiesFromCommit(
424
- event.repository.url,
425
- branch,
426
- event.commits,
427
- (commit) => [...commit.added]
428
- );
429
- const removed = this.collectDeferredEntitiesFromCommit(
430
- event.repository.url,
431
- branch,
432
- event.commits,
433
- (commit) => [...commit.removed]
434
- );
435
- const modified = this.collectFilesFromCommit(
436
- event.commits,
437
- (commit) => [...commit.modified]
438
- );
439
- if (modified.length > 0) {
440
- await this.connection.refresh({
441
- keys: [
442
- ...modified.map(
443
- (filePath) => `url:${event.repository.url}/tree/${branch}/${filePath}`
444
- ),
445
- ...modified.map(
446
- (filePath) => `url:${event.repository.url}/blob/${branch}/${filePath}`
447
- )
448
- ]
449
- });
450
- }
451
- if (added.length > 0 || removed.length > 0) {
452
- await this.connection.applyMutation({
453
- type: "delta",
454
- added,
455
- removed
456
- });
457
- }
458
- this.logger.info(
459
- `Processed Github push event: added ${added.length} - removed ${removed.length} - modified ${modified.length}`
460
- );
461
- }
462
- collectDeferredEntitiesFromCommit(repositoryUrl, branch, commits, transformOperation) {
463
- const catalogFiles = this.collectFilesFromCommit(
464
- commits,
465
- transformOperation
466
- );
467
- return this.toDeferredEntities(
468
- catalogFiles.map(
469
- (filePath) => `${repositoryUrl}/blob/${branch}/${filePath}`
470
- )
471
- );
472
- }
473
- collectFilesFromCommit(commits, transformOperation) {
474
- const catalogFile = this.config.catalogPath.startsWith("/") ? this.config.catalogPath.substring(1) : this.config.catalogPath;
475
- const matcher = new minimatch.Minimatch(catalogFile);
476
- return commits.map(transformOperation).flat().filter((file) => matcher.match(file));
477
- }
478
- toDeferredEntities(targets) {
479
- return targets.map((target) => {
480
- const location = GithubEntityProvider.toLocationSpec(target);
481
- return pluginCatalogNode.locationSpecToLocationEntity({ location });
482
- }).map((entity) => {
483
- return {
484
- locationKey: this.getProviderName(),
485
- entity
486
- };
487
- });
488
- }
489
- }
16
+ require('minimatch');
490
17
 
491
18
  const catalogModuleGithubEntityProvider = backendPluginApi.createBackendModule({
492
19
  pluginId: "catalog",
@@ -501,7 +28,7 @@ const catalogModuleGithubEntityProvider = backendPluginApi.createBackendModule({
501
28
  },
502
29
  async init({ catalog, config, logger, scheduler }) {
503
30
  catalog.addEntityProvider(
504
- GithubEntityProvider.fromConfig(config, {
31
+ GithubEntityProvider.GithubEntityProvider.fromConfig(config, {
505
32
  logger: backendCommon.loggerToWinstonLogger(logger),
506
33
  scheduler
507
34
  })