@backstage/plugin-catalog-backend-module-azure 0.0.0-nightly-20220308022132

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 ADDED
@@ -0,0 +1,14 @@
1
+ # @backstage/plugin-catalog-backend-module-azure
2
+
3
+ ## 0.0.0-nightly-20220308022132
4
+
5
+ ### Minor Changes
6
+
7
+ - 66ba5d9023: Added package, moving out azure specific functionality from the catalog-backend
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @backstage/plugin-catalog-backend@0.0.0-nightly-20220308022132
13
+ - @backstage/backend-common@0.0.0-nightly-20220308022132
14
+ - @backstage/catalog-model@0.0.0-nightly-20220308022132
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # Catalog Backend Module for Azure
2
+
3
+ This is an extension module to the plugin-catalog-backend plugin, providing extensions targeted at Azure cloud offerings.
4
+
5
+ ## Getting started
6
+
7
+ See [Backstage documentation](https://backstage.io/docs/integrations/azure/org) for details on how to install
8
+ and configure the plugin.
@@ -0,0 +1,105 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var integration = require('@backstage/integration');
6
+ var pluginCatalogBackend = require('@backstage/plugin-catalog-backend');
7
+ var fetch = require('node-fetch');
8
+
9
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
10
+
11
+ var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
12
+
13
+ const isCloud = (host) => host === "dev.azure.com";
14
+ const PAGE_SIZE = 1e3;
15
+ async function codeSearch(azureConfig, org, project, repo, path) {
16
+ const searchBaseUrl = isCloud(azureConfig.host) ? "https://almsearch.dev.azure.com" : `https://${azureConfig.host}`;
17
+ const searchUrl = `${searchBaseUrl}/${org}/${project}/_apis/search/codesearchresults?api-version=6.0-preview.1`;
18
+ let items = [];
19
+ let hasMorePages = true;
20
+ do {
21
+ const response = await fetch__default["default"](searchUrl, {
22
+ ...integration.getAzureRequestOptions(azureConfig, {
23
+ "Content-Type": "application/json"
24
+ }),
25
+ method: "POST",
26
+ body: JSON.stringify({
27
+ searchText: `path:${path} repo:${repo || "*"}`,
28
+ $skip: items.length,
29
+ $top: PAGE_SIZE
30
+ })
31
+ });
32
+ if (response.status !== 200) {
33
+ throw new Error(`Azure DevOps search failed with response status ${response.status}`);
34
+ }
35
+ const body = await response.json();
36
+ items = [...items, ...body.results];
37
+ hasMorePages = body.count > items.length;
38
+ } while (hasMorePages);
39
+ return items;
40
+ }
41
+
42
+ class AzureDevOpsDiscoveryProcessor {
43
+ static fromConfig(config, options) {
44
+ const integrations = integration.ScmIntegrations.fromConfig(config);
45
+ return new AzureDevOpsDiscoveryProcessor({
46
+ ...options,
47
+ integrations
48
+ });
49
+ }
50
+ constructor(options) {
51
+ this.integrations = options.integrations;
52
+ this.logger = options.logger;
53
+ }
54
+ getProcessorName() {
55
+ return "AzureDevOpsDiscoveryProcessor";
56
+ }
57
+ async readLocation(location, _optional, emit) {
58
+ var _a;
59
+ if (location.type !== "azure-discovery") {
60
+ return false;
61
+ }
62
+ const azureConfig = (_a = this.integrations.azure.byUrl(location.target)) == null ? void 0 : _a.config;
63
+ if (!azureConfig) {
64
+ throw new Error(`There is no Azure integration that matches ${location.target}. Please add a configuration entry for it under integrations.azure`);
65
+ }
66
+ const { baseUrl, org, project, repo, catalogPath } = parseUrl(location.target);
67
+ this.logger.info(`Reading Azure DevOps repositories from ${location.target}`);
68
+ const files = await codeSearch(azureConfig, org, project, repo, catalogPath);
69
+ this.logger.debug(`Found ${files.length} files in Azure DevOps from ${location.target}.`);
70
+ for (const file of files) {
71
+ emit(pluginCatalogBackend.processingResult.location({
72
+ type: "url",
73
+ target: `${baseUrl}/${org}/${project}/_git/${file.repository.name}?path=${file.path}`,
74
+ presence: "optional"
75
+ }));
76
+ }
77
+ return true;
78
+ }
79
+ }
80
+ function parseUrl(urlString) {
81
+ const url = new URL(urlString);
82
+ const path = url.pathname.substr(1).split("/");
83
+ const catalogPath = url.searchParams.get("path") || "/catalog-info.yaml";
84
+ if (path.length === 2 && path[0].length && path[1].length) {
85
+ return {
86
+ baseUrl: url.origin,
87
+ org: decodeURIComponent(path[0]),
88
+ project: decodeURIComponent(path[1]),
89
+ repo: "",
90
+ catalogPath
91
+ };
92
+ } else if (path.length === 4 && path[0].length && path[1].length && path[2].length && path[3].length) {
93
+ return {
94
+ baseUrl: url.origin,
95
+ org: decodeURIComponent(path[0]),
96
+ project: decodeURIComponent(path[1]),
97
+ repo: decodeURIComponent(path[3]),
98
+ catalogPath
99
+ };
100
+ }
101
+ throw new Error(`Failed to parse ${urlString}`);
102
+ }
103
+
104
+ exports.AzureDevOpsDiscoveryProcessor = AzureDevOpsDiscoveryProcessor;
105
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/lib/azure.ts","../src/AzureDevOpsDiscoveryProcessor.ts"],"sourcesContent":["/*\n * Copyright 2021 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 fetch from 'node-fetch';\nimport {\n AzureIntegrationConfig,\n getAzureRequestOptions,\n} from '@backstage/integration';\n\nexport interface CodeSearchResponse {\n count: number;\n results: CodeSearchResultItem[];\n}\n\nexport interface CodeSearchResultItem {\n fileName: string;\n path: string;\n repository: {\n name: string;\n };\n}\n\nconst isCloud = (host: string) => host === 'dev.azure.com';\nconst PAGE_SIZE = 1000;\n\n// codeSearch returns all files that matches the given search path.\nexport async function codeSearch(\n azureConfig: AzureIntegrationConfig,\n org: string,\n project: string,\n repo: string,\n path: string,\n): Promise<CodeSearchResultItem[]> {\n const searchBaseUrl = isCloud(azureConfig.host)\n ? 'https://almsearch.dev.azure.com'\n : `https://${azureConfig.host}`;\n const searchUrl = `${searchBaseUrl}/${org}/${project}/_apis/search/codesearchresults?api-version=6.0-preview.1`;\n\n let items: CodeSearchResultItem[] = [];\n let hasMorePages = true;\n\n do {\n const response = await fetch(searchUrl, {\n ...getAzureRequestOptions(azureConfig, {\n 'Content-Type': 'application/json',\n }),\n method: 'POST',\n body: JSON.stringify({\n searchText: `path:${path} repo:${repo || '*'}`,\n $skip: items.length,\n $top: PAGE_SIZE,\n }),\n });\n\n if (response.status !== 200) {\n throw new Error(\n `Azure DevOps search failed with response status ${response.status}`,\n );\n }\n\n const body: CodeSearchResponse = await response.json();\n items = [...items, ...body.results];\n hasMorePages = body.count > items.length;\n } while (hasMorePages);\n\n return items;\n}\n","/*\n * Copyright 2021 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 { Config } from '@backstage/config';\nimport {\n ScmIntegrationRegistry,\n ScmIntegrations,\n} from '@backstage/integration';\nimport {\n CatalogProcessor,\n CatalogProcessorEmit,\n LocationSpec,\n processingResult,\n} from '@backstage/plugin-catalog-backend';\nimport { Logger } from 'winston';\nimport { codeSearch } from './lib';\n\n/**\n * Extracts repositories out of an Azure DevOps org.\n *\n * The following will create locations for all projects which have a catalog-info.yaml\n * on the default branch. The first is shorthand for the second.\n *\n * target: \"https://dev.azure.com/org/project\"\n * or\n * target: https://dev.azure.com/org/project?path=/catalog-info.yaml\n *\n * You may also explicitly specify a single repo:\n *\n * target: https://dev.azure.com/org/project/_git/repo\n *\n * @public\n **/\nexport class AzureDevOpsDiscoveryProcessor implements CatalogProcessor {\n private readonly integrations: ScmIntegrationRegistry;\n private readonly logger: Logger;\n\n static fromConfig(config: Config, options: { logger: Logger }) {\n const integrations = ScmIntegrations.fromConfig(config);\n\n return new AzureDevOpsDiscoveryProcessor({\n ...options,\n integrations,\n });\n }\n\n constructor(options: {\n integrations: ScmIntegrationRegistry;\n logger: Logger;\n }) {\n this.integrations = options.integrations;\n this.logger = options.logger;\n }\n\n getProcessorName(): string {\n return 'AzureDevOpsDiscoveryProcessor';\n }\n\n async readLocation(\n location: LocationSpec,\n _optional: boolean,\n emit: CatalogProcessorEmit,\n ): Promise<boolean> {\n if (location.type !== 'azure-discovery') {\n return false;\n }\n\n const azureConfig = this.integrations.azure.byUrl(location.target)?.config;\n if (!azureConfig) {\n throw new Error(\n `There is no Azure integration that matches ${location.target}. Please add a configuration entry for it under integrations.azure`,\n );\n }\n\n const { baseUrl, org, project, repo, catalogPath } = parseUrl(\n location.target,\n );\n this.logger.info(\n `Reading Azure DevOps repositories from ${location.target}`,\n );\n\n const files = await codeSearch(\n azureConfig,\n org,\n project,\n repo,\n catalogPath,\n );\n\n this.logger.debug(\n `Found ${files.length} files in Azure DevOps from ${location.target}.`,\n );\n\n for (const file of files) {\n emit(\n processingResult.location({\n type: 'url',\n target: `${baseUrl}/${org}/${project}/_git/${file.repository.name}?path=${file.path}`,\n // Not all locations may actually exist, since the user defined them as a wildcard pattern.\n // Thus, we emit them as optional and let the downstream processor find them while not outputting\n // an error if it couldn't.\n presence: 'optional',\n }),\n );\n }\n\n return true;\n }\n}\n\n/**\n * parseUrl extracts segments from the Azure DevOps URL.\n **/\nexport function parseUrl(urlString: string): {\n baseUrl: string;\n org: string;\n project: string;\n repo: string;\n catalogPath: string;\n} {\n const url = new URL(urlString);\n const path = url.pathname.substr(1).split('/');\n\n const catalogPath = url.searchParams.get('path') || '/catalog-info.yaml';\n\n if (path.length === 2 && path[0].length && path[1].length) {\n return {\n baseUrl: url.origin,\n org: decodeURIComponent(path[0]),\n project: decodeURIComponent(path[1]),\n repo: '',\n catalogPath,\n };\n } else if (\n path.length === 4 &&\n path[0].length &&\n path[1].length &&\n path[2].length &&\n path[3].length\n ) {\n return {\n baseUrl: url.origin,\n org: decodeURIComponent(path[0]),\n project: decodeURIComponent(path[1]),\n repo: decodeURIComponent(path[3]),\n catalogPath,\n };\n }\n\n throw new Error(`Failed to parse ${urlString}`);\n}\n"],"names":["fetch","getAzureRequestOptions","ScmIntegrations","processingResult"],"mappings":";;;;;;;;;;;;AAmCA,MAAM,UAAU,CAAC,SAAiB,SAAS;AAC3C,MAAM,YAAY;0BAIhB,aACA,KACA,SACA,MACA,MACiC;AACjC,QAAM,gBAAgB,QAAQ,YAAY,QACtC,oCACA,WAAW,YAAY;AAC3B,QAAM,YAAY,GAAG,iBAAiB,OAAO;AAE7C,MAAI,QAAgC;AACpC,MAAI,eAAe;AAEnB,KAAG;AACD,UAAM,WAAW,MAAMA,0BAAM,WAAW;AAAA,SACnCC,mCAAuB,aAAa;AAAA,QACrC,gBAAgB;AAAA;AAAA,MAElB,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU;AAAA,QACnB,YAAY,QAAQ,aAAa,QAAQ;AAAA,QACzC,OAAO,MAAM;AAAA,QACb,MAAM;AAAA;AAAA;AAIV,QAAI,SAAS,WAAW,KAAK;AAC3B,YAAM,IAAI,MACR,mDAAmD,SAAS;AAAA;AAIhE,UAAM,OAA2B,MAAM,SAAS;AAChD,YAAQ,CAAC,GAAG,OAAO,GAAG,KAAK;AAC3B,mBAAe,KAAK,QAAQ,MAAM;AAAA,WAC3B;AAET,SAAO;AAAA;;oCChC8D;AAAA,SAI9D,WAAW,QAAgB,SAA6B;AAC7D,UAAM,eAAeC,4BAAgB,WAAW;AAEhD,WAAO,IAAI,8BAA8B;AAAA,SACpC;AAAA,MACH;AAAA;AAAA;AAAA,EAIJ,YAAY,SAGT;AACD,SAAK,eAAe,QAAQ;AAC5B,SAAK,SAAS,QAAQ;AAAA;AAAA,EAGxB,mBAA2B;AACzB,WAAO;AAAA;AAAA,QAGH,aACJ,UACA,WACA,MACkB;AA3EtB;AA4EI,QAAI,SAAS,SAAS,mBAAmB;AACvC,aAAO;AAAA;AAGT,UAAM,cAAc,WAAK,aAAa,MAAM,MAAM,SAAS,YAAvC,mBAAgD;AACpE,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI,MACR,8CAA8C,SAAS;AAAA;AAI3D,UAAM,EAAE,SAAS,KAAK,SAAS,MAAM,gBAAgB,SACnD,SAAS;AAEX,SAAK,OAAO,KACV,0CAA0C,SAAS;AAGrD,UAAM,QAAQ,MAAM,WAClB,aACA,KACA,SACA,MACA;AAGF,SAAK,OAAO,MACV,SAAS,MAAM,qCAAqC,SAAS;AAG/D,eAAW,QAAQ,OAAO;AACxB,WACEC,sCAAiB,SAAS;AAAA,QACxB,MAAM;AAAA,QACN,QAAQ,GAAG,WAAW,OAAO,gBAAgB,KAAK,WAAW,aAAa,KAAK;AAAA,QAI/E,UAAU;AAAA;AAAA;AAKhB,WAAO;AAAA;AAAA;kBAOc,WAMvB;AACA,QAAM,MAAM,IAAI,IAAI;AACpB,QAAM,OAAO,IAAI,SAAS,OAAO,GAAG,MAAM;AAE1C,QAAM,cAAc,IAAI,aAAa,IAAI,WAAW;AAEpD,MAAI,KAAK,WAAW,KAAK,KAAK,GAAG,UAAU,KAAK,GAAG,QAAQ;AACzD,WAAO;AAAA,MACL,SAAS,IAAI;AAAA,MACb,KAAK,mBAAmB,KAAK;AAAA,MAC7B,SAAS,mBAAmB,KAAK;AAAA,MACjC,MAAM;AAAA,MACN;AAAA;AAAA,aAGF,KAAK,WAAW,KAChB,KAAK,GAAG,UACR,KAAK,GAAG,UACR,KAAK,GAAG,UACR,KAAK,GAAG,QACR;AACA,WAAO;AAAA,MACL,SAAS,IAAI;AAAA,MACb,KAAK,mBAAmB,KAAK;AAAA,MAC7B,SAAS,mBAAmB,KAAK;AAAA,MACjC,MAAM,mBAAmB,KAAK;AAAA,MAC9B;AAAA;AAAA;AAIJ,QAAM,IAAI,MAAM,mBAAmB;AAAA;;;;"}
@@ -0,0 +1,36 @@
1
+ import { Config } from '@backstage/config';
2
+ import { ScmIntegrationRegistry } from '@backstage/integration';
3
+ import { CatalogProcessor, LocationSpec, CatalogProcessorEmit } from '@backstage/plugin-catalog-backend';
4
+ import { Logger } from 'winston';
5
+
6
+ /**
7
+ * Extracts repositories out of an Azure DevOps org.
8
+ *
9
+ * The following will create locations for all projects which have a catalog-info.yaml
10
+ * on the default branch. The first is shorthand for the second.
11
+ *
12
+ * target: "https://dev.azure.com/org/project"
13
+ * or
14
+ * target: https://dev.azure.com/org/project?path=/catalog-info.yaml
15
+ *
16
+ * You may also explicitly specify a single repo:
17
+ *
18
+ * target: https://dev.azure.com/org/project/_git/repo
19
+ *
20
+ * @public
21
+ **/
22
+ declare class AzureDevOpsDiscoveryProcessor implements CatalogProcessor {
23
+ private readonly integrations;
24
+ private readonly logger;
25
+ static fromConfig(config: Config, options: {
26
+ logger: Logger;
27
+ }): AzureDevOpsDiscoveryProcessor;
28
+ constructor(options: {
29
+ integrations: ScmIntegrationRegistry;
30
+ logger: Logger;
31
+ });
32
+ getProcessorName(): string;
33
+ readLocation(location: LocationSpec, _optional: boolean, emit: CatalogProcessorEmit): Promise<boolean>;
34
+ }
35
+
36
+ export { AzureDevOpsDiscoveryProcessor };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@backstage/plugin-catalog-backend-module-azure",
3
+ "description": "A Backstage catalog backend module that helps integrate towards Azure",
4
+ "version": "0.0.0-nightly-20220308022132",
5
+ "main": "dist/index.cjs.js",
6
+ "types": "dist/index.d.ts",
7
+ "license": "Apache-2.0",
8
+ "private": false,
9
+ "publishConfig": {
10
+ "access": "public",
11
+ "main": "dist/index.cjs.js",
12
+ "types": "dist/index.d.ts"
13
+ },
14
+ "backstage": {
15
+ "role": "backend-plugin-module"
16
+ },
17
+ "homepage": "https://backstage.io",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/backstage/backstage",
21
+ "directory": "plugins/catalog-backend-module-azure"
22
+ },
23
+ "keywords": [
24
+ "backstage"
25
+ ],
26
+ "scripts": {
27
+ "build": "backstage-cli package build",
28
+ "lint": "backstage-cli package lint",
29
+ "test": "backstage-cli package test",
30
+ "prepack": "backstage-cli package prepack",
31
+ "postpack": "backstage-cli package postpack",
32
+ "clean": "backstage-cli package clean",
33
+ "start": "backstage-cli package start"
34
+ },
35
+ "dependencies": {
36
+ "@backstage/backend-common": "^0.0.0-nightly-20220308022132",
37
+ "@backstage/catalog-model": "^0.0.0-nightly-20220308022132",
38
+ "@backstage/config": "^0.1.15",
39
+ "@backstage/errors": "^0.2.2",
40
+ "@backstage/integration": "^0.8.0",
41
+ "@backstage/plugin-catalog-backend": "^0.0.0-nightly-20220308022132",
42
+ "@backstage/types": "^0.1.3",
43
+ "lodash": "^4.17.21",
44
+ "msw": "^0.35.0",
45
+ "node-fetch": "^2.6.7",
46
+ "winston": "^3.2.1"
47
+ },
48
+ "devDependencies": {
49
+ "@backstage/backend-test-utils": "^0.0.0-nightly-20220308022132",
50
+ "@backstage/cli": "^0.0.0-nightly-20220308022132",
51
+ "@types/lodash": "^4.14.151"
52
+ },
53
+ "files": [
54
+ "dist"
55
+ ]
56
+ }