@backstage/backend-defaults 0.9.0-next.1 → 0.9.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.
Files changed (24) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/config.d.ts +88 -5
  3. package/dist/discovery.d.ts +105 -36
  4. package/dist/entrypoints/auditor/auditorServiceFactory.cjs.js +33 -7
  5. package/dist/entrypoints/auditor/auditorServiceFactory.cjs.js.map +1 -1
  6. package/dist/entrypoints/auth/DefaultAuthService.cjs.js +2 -1
  7. package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
  8. package/dist/entrypoints/auth/helpers.cjs.js +5 -2
  9. package/dist/entrypoints/auth/helpers.cjs.js.map +1 -1
  10. package/dist/entrypoints/discovery/HostDiscovery.cjs.js +128 -53
  11. package/dist/entrypoints/discovery/HostDiscovery.cjs.js.map +1 -1
  12. package/dist/entrypoints/discovery/SrvResolvers.cjs.js +148 -0
  13. package/dist/entrypoints/discovery/SrvResolvers.cjs.js.map +1 -0
  14. package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js +7 -3
  15. package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js.map +1 -1
  16. package/dist/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.cjs.js +23 -1
  17. package/dist/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.cjs.js.map +1 -1
  18. package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js +4 -0
  19. package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js.map +1 -1
  20. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +3 -14
  21. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
  22. package/dist/package.json.cjs.js +1 -2
  23. package/dist/package.json.cjs.js.map +1 -1
  24. package/package.json +20 -21
@@ -1,41 +1,56 @@
1
1
  'use strict';
2
2
 
3
3
  var config = require('../rootHttpRouter/http/config.cjs.js');
4
+ var SrvResolvers = require('./SrvResolvers.cjs.js');
5
+ var lodash = require('lodash');
4
6
 
5
7
  class HostDiscovery {
6
- constructor(internalBaseUrl, externalBaseUrl, discoveryConfig) {
7
- this.internalBaseUrl = internalBaseUrl;
8
- this.externalBaseUrl = externalBaseUrl;
9
- this.discoveryConfig = discoveryConfig;
8
+ #srvResolver;
9
+ #internalResolvers = /* @__PURE__ */ new Map();
10
+ #externalResolvers = /* @__PURE__ */ new Map();
11
+ #internalFallbackResolver = async () => {
12
+ throw new Error("Not initialized");
13
+ };
14
+ #externalFallbackResolver = async () => {
15
+ throw new Error("Not initialized");
16
+ };
17
+ static fromConfig(config, options) {
18
+ const discovery = new HostDiscovery(new SrvResolvers.SrvResolvers());
19
+ discovery.#updateResolvers(config, options?.defaultEndpoints);
20
+ config.subscribe?.(() => {
21
+ try {
22
+ discovery.#updateResolvers(config, options?.defaultEndpoints);
23
+ } catch (e) {
24
+ options?.logger.error(`Failed to update discovery service: ${e}`);
25
+ }
26
+ });
27
+ return discovery;
10
28
  }
11
- /**
12
- * Creates a new HostDiscovery discovery instance by reading
13
- * from the `backend` config section, specifically the `.baseUrl` for
14
- * discovering the external URL, and the `.listen` and `.https` config
15
- * for the internal one.
16
- *
17
- * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`.
18
- * eg.
19
- *
20
- * ```yaml
21
- * discovery:
22
- * endpoints:
23
- * - target: https://internal.example.com/internal-catalog
24
- * plugins: [catalog]
25
- * - target: https://internal.example.com/secure/api/{{pluginId}}
26
- * plugins: [auth, permission]
27
- * - target:
28
- * internal: https://internal.example.com/search
29
- * external: https://example.com/search
30
- * plugins: [search]
31
- * ```
32
- *
33
- * The fixed base path is `/api`, meaning the default full internal
34
- * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`.
35
- */
36
- static fromConfig(config$1) {
37
- const basePath = "/api";
38
- const externalBaseUrl = config$1.getString("backend.baseUrl").replace(/\/+$/, "");
29
+ constructor(srvResolver) {
30
+ this.#srvResolver = srvResolver;
31
+ this.#internalResolvers = /* @__PURE__ */ new Map();
32
+ this.#externalResolvers = /* @__PURE__ */ new Map();
33
+ this.#internalFallbackResolver = () => {
34
+ throw new Error("Not initialized");
35
+ };
36
+ this.#externalFallbackResolver = () => {
37
+ throw new Error("Not initialized");
38
+ };
39
+ }
40
+ async getBaseUrl(pluginId) {
41
+ const resolver = this.#internalResolvers.get(pluginId) ?? this.#internalResolvers.get("*") ?? this.#internalFallbackResolver;
42
+ return await resolver(pluginId);
43
+ }
44
+ async getExternalBaseUrl(pluginId) {
45
+ const resolver = this.#externalResolvers.get(pluginId) ?? this.#externalResolvers.get("*") ?? this.#externalFallbackResolver;
46
+ return await resolver(pluginId);
47
+ }
48
+ #updateResolvers(config, defaultEndpoints) {
49
+ this.#updateFallbackResolvers(config);
50
+ this.#updatePluginResolvers(config, defaultEndpoints);
51
+ }
52
+ #updateFallbackResolvers(config$1) {
53
+ const backendBaseUrl = lodash.trimEnd(config$1.getString("backend.baseUrl"), "/");
39
54
  const {
40
55
  listen: { host: listenHost = "::", port: listenPort }
41
56
  } = config.readHttpServerOptions(config$1.getConfig("backend"));
@@ -49,31 +64,91 @@ class HostDiscovery {
49
64
  if (host.includes(":")) {
50
65
  host = `[${host}]`;
51
66
  }
52
- const internalBaseUrl = `${protocol}://${host}:${listenPort}`;
53
- return new HostDiscovery(
54
- internalBaseUrl + basePath,
55
- externalBaseUrl + basePath,
56
- config$1.getOptionalConfig("discovery")
67
+ this.#internalFallbackResolver = this.#makeResolver(
68
+ `${protocol}://${host}:${listenPort}/api/{{pluginId}}`,
69
+ false
57
70
  );
58
- }
59
- getTargetFromConfig(pluginId, type) {
60
- const endpoints = this.discoveryConfig?.getOptionalConfigArray("endpoints");
61
- const targetOrObj = endpoints?.find((endpoint) => endpoint.getStringArray("plugins").includes(pluginId))?.get("target");
62
- const target = typeof targetOrObj === "string" ? targetOrObj : targetOrObj?.[type];
63
- if (!target) {
64
- const baseUrl = type === "external" ? this.externalBaseUrl : this.internalBaseUrl;
65
- return `${baseUrl}/${encodeURIComponent(pluginId)}`;
66
- }
67
- return target.replace(
68
- /\{\{\s*pluginId\s*\}\}/g,
69
- encodeURIComponent(pluginId)
71
+ this.#externalFallbackResolver = this.#makeResolver(
72
+ `${backendBaseUrl}/api/{{pluginId}}`,
73
+ false
70
74
  );
71
75
  }
72
- async getBaseUrl(pluginId) {
73
- return this.getTargetFromConfig(pluginId, "internal");
76
+ #updatePluginResolvers(config, defaultEndpoints) {
77
+ const endpoints = defaultEndpoints?.slice() ?? [];
78
+ const endpointConfigs = config.getOptionalConfigArray(
79
+ "discovery.endpoints"
80
+ );
81
+ for (const endpointConfig of endpointConfigs ?? []) {
82
+ if (typeof endpointConfig.get("target") === "string") {
83
+ endpoints.push({
84
+ target: endpointConfig.getString("target"),
85
+ plugins: endpointConfig.getStringArray("plugins")
86
+ });
87
+ } else {
88
+ endpoints.push({
89
+ target: {
90
+ internal: endpointConfig.getOptionalString("target.internal"),
91
+ external: endpointConfig.getOptionalString("target.external")
92
+ },
93
+ plugins: endpointConfig.getStringArray("plugins")
94
+ });
95
+ }
96
+ }
97
+ const internalResolvers = /* @__PURE__ */ new Map();
98
+ const externalResolvers = /* @__PURE__ */ new Map();
99
+ for (const { target, plugins } of endpoints) {
100
+ let internalResolver;
101
+ let externalResolver;
102
+ if (typeof target === "string") {
103
+ internalResolver = externalResolver = this.#makeResolver(target, false);
104
+ } else {
105
+ if (target.internal) {
106
+ internalResolver = this.#makeResolver(target.internal, true);
107
+ }
108
+ if (target.external) {
109
+ externalResolver = this.#makeResolver(target.external, false);
110
+ }
111
+ }
112
+ if (internalResolver) {
113
+ for (const pluginId of plugins) {
114
+ internalResolvers.set(pluginId, internalResolver);
115
+ }
116
+ }
117
+ if (externalResolver) {
118
+ for (const pluginId of plugins) {
119
+ externalResolvers.set(pluginId, externalResolver);
120
+ }
121
+ }
122
+ }
123
+ this.#internalResolvers = internalResolvers;
124
+ this.#externalResolvers = externalResolvers;
74
125
  }
75
- async getExternalBaseUrl(pluginId) {
76
- return this.getTargetFromConfig(pluginId, "external");
126
+ #makeResolver(urlPattern, allowSrv) {
127
+ const withPluginId = (pluginId, url) => {
128
+ return url.replace(
129
+ /\{\{\s*pluginId\s*\}\}/g,
130
+ encodeURIComponent(pluginId)
131
+ );
132
+ };
133
+ if (!this.#srvResolver.isSrvUrl(urlPattern)) {
134
+ return async (pluginId) => withPluginId(pluginId, urlPattern);
135
+ }
136
+ if (!allowSrv) {
137
+ throw new Error(
138
+ `SRV resolver URLs cannot be used in the target for external endpoints`
139
+ );
140
+ }
141
+ const lazyResolvers = /* @__PURE__ */ new Map();
142
+ return async (pluginId) => {
143
+ let lazyResolver = lazyResolvers.get(pluginId);
144
+ if (!lazyResolver) {
145
+ lazyResolver = this.#srvResolver.getResolver(
146
+ withPluginId(pluginId, urlPattern)
147
+ );
148
+ lazyResolvers.set(pluginId, lazyResolver);
149
+ }
150
+ return await lazyResolver();
151
+ };
77
152
  }
78
153
  }
79
154
 
@@ -1 +1 @@
1
- {"version":3,"file":"HostDiscovery.cjs.js","sources":["../../../src/entrypoints/discovery/HostDiscovery.ts"],"sourcesContent":["/*\n * Copyright 2020 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 DiscoveryService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { readHttpServerOptions } from '../rootHttpRouter/http/config';\n\ntype Target = string | { internal: string; external: string };\n\n/**\n * HostDiscovery is a basic DiscoveryService implementation\n * that can handle plugins that are hosted in a single or multiple deployments.\n *\n * The deployment may be scaled horizontally, as long as the external URL\n * is the same for all instances. However, internal URLs will always be\n * resolved to the same host, so there won't be any balancing of internal traffic.\n *\n * @public\n */\nexport class HostDiscovery implements DiscoveryService {\n /**\n * Creates a new HostDiscovery discovery instance by reading\n * from the `backend` config section, specifically the `.baseUrl` for\n * discovering the external URL, and the `.listen` and `.https` config\n * for the internal one.\n *\n * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`.\n * eg.\n *\n * ```yaml\n * discovery:\n * endpoints:\n * - target: https://internal.example.com/internal-catalog\n * plugins: [catalog]\n * - target: https://internal.example.com/secure/api/{{pluginId}}\n * plugins: [auth, permission]\n * - target:\n * internal: https://internal.example.com/search\n * external: https://example.com/search\n * plugins: [search]\n * ```\n *\n * The fixed base path is `/api`, meaning the default full internal\n * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`.\n */\n static fromConfig(config: RootConfigService) {\n const basePath = '/api';\n const externalBaseUrl = config\n .getString('backend.baseUrl')\n .replace(/\\/+$/, '');\n\n const {\n listen: { host: listenHost = '::', port: listenPort },\n } = readHttpServerOptions(config.getConfig('backend'));\n const protocol = config.has('backend.https') ? 'https' : 'http';\n\n // Translate bind-all to localhost, and support IPv6\n let host = listenHost;\n if (host === '::' || host === '') {\n // We use localhost instead of ::1, since IPv6-compatible systems should default\n // to using IPv6 when they see localhost, but if the system doesn't support IPv6\n // things will still work.\n host = 'localhost';\n } else if (host === '0.0.0.0') {\n host = '127.0.0.1';\n }\n if (host.includes(':')) {\n host = `[${host}]`;\n }\n\n const internalBaseUrl = `${protocol}://${host}:${listenPort}`;\n\n return new HostDiscovery(\n internalBaseUrl + basePath,\n externalBaseUrl + basePath,\n config.getOptionalConfig('discovery'),\n );\n }\n\n private constructor(\n private readonly internalBaseUrl: string,\n private readonly externalBaseUrl: string,\n private readonly discoveryConfig: Config | undefined,\n ) {}\n\n private getTargetFromConfig(pluginId: string, type: 'internal' | 'external') {\n const endpoints = this.discoveryConfig?.getOptionalConfigArray('endpoints');\n\n const targetOrObj = endpoints\n ?.find(endpoint => endpoint.getStringArray('plugins').includes(pluginId))\n ?.get<Target>('target');\n\n const target =\n typeof targetOrObj === 'string' ? targetOrObj : targetOrObj?.[type];\n\n if (!target) {\n const baseUrl =\n type === 'external' ? this.externalBaseUrl : this.internalBaseUrl;\n\n return `${baseUrl}/${encodeURIComponent(pluginId)}`;\n }\n\n return target.replace(\n /\\{\\{\\s*pluginId\\s*\\}\\}/g,\n encodeURIComponent(pluginId),\n );\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n return this.getTargetFromConfig(pluginId, 'internal');\n }\n\n async getExternalBaseUrl(pluginId: string): Promise<string> {\n return this.getTargetFromConfig(pluginId, 'external');\n }\n}\n"],"names":["config","readHttpServerOptions"],"mappings":";;;;AAmCO,MAAM,aAA0C,CAAA;AAAA,EA4D7C,WAAA,CACW,eACA,EAAA,eAAA,EACA,eACjB,EAAA;AAHiB,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AACA,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AACA,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AAAA;AAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAtCH,OAAO,WAAWA,QAA2B,EAAA;AAC3C,IAAA,MAAM,QAAW,GAAA,MAAA;AACjB,IAAA,MAAM,kBAAkBA,QACrB,CAAA,SAAA,CAAU,iBAAiB,CAC3B,CAAA,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAErB,IAAM,MAAA;AAAA,MACJ,QAAQ,EAAE,IAAA,EAAM,UAAa,GAAA,IAAA,EAAM,MAAM,UAAW;AAAA,KAClD,GAAAC,4BAAA,CAAsBD,QAAO,CAAA,SAAA,CAAU,SAAS,CAAC,CAAA;AACrD,IAAA,MAAM,QAAW,GAAAA,QAAA,CAAO,GAAI,CAAA,eAAe,IAAI,OAAU,GAAA,MAAA;AAGzD,IAAA,IAAI,IAAO,GAAA,UAAA;AACX,IAAI,IAAA,IAAA,KAAS,IAAQ,IAAA,IAAA,KAAS,EAAI,EAAA;AAIhC,MAAO,IAAA,GAAA,WAAA;AAAA,KACT,MAAA,IAAW,SAAS,SAAW,EAAA;AAC7B,MAAO,IAAA,GAAA,WAAA;AAAA;AAET,IAAI,IAAA,IAAA,CAAK,QAAS,CAAA,GAAG,CAAG,EAAA;AACtB,MAAA,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA,CAAA;AAAA;AAGjB,IAAA,MAAM,kBAAkB,CAAG,EAAA,QAAQ,CAAM,GAAA,EAAA,IAAI,IAAI,UAAU,CAAA,CAAA;AAE3D,IAAA,OAAO,IAAI,aAAA;AAAA,MACT,eAAkB,GAAA,QAAA;AAAA,MAClB,eAAkB,GAAA,QAAA;AAAA,MAClBA,QAAA,CAAO,kBAAkB,WAAW;AAAA,KACtC;AAAA;AACF,EAQQ,mBAAA,CAAoB,UAAkB,IAA+B,EAAA;AAC3E,IAAA,MAAM,SAAY,GAAA,IAAA,CAAK,eAAiB,EAAA,sBAAA,CAAuB,WAAW,CAAA;AAE1E,IAAA,MAAM,WAAc,GAAA,SAAA,EAChB,IAAK,CAAA,CAAA,QAAA,KAAY,QAAS,CAAA,cAAA,CAAe,SAAS,CAAA,CAAE,QAAS,CAAA,QAAQ,CAAC,CAAA,EACtE,IAAY,QAAQ,CAAA;AAExB,IAAA,MAAM,SACJ,OAAO,WAAA,KAAgB,QAAW,GAAA,WAAA,GAAc,cAAc,IAAI,CAAA;AAEpE,IAAA,IAAI,CAAC,MAAQ,EAAA;AACX,MAAA,MAAM,OACJ,GAAA,IAAA,KAAS,UAAa,GAAA,IAAA,CAAK,kBAAkB,IAAK,CAAA,eAAA;AAEpD,MAAA,OAAO,CAAG,EAAA,OAAO,CAAI,CAAA,EAAA,kBAAA,CAAmB,QAAQ,CAAC,CAAA,CAAA;AAAA;AAGnD,IAAA,OAAO,MAAO,CAAA,OAAA;AAAA,MACZ,yBAAA;AAAA,MACA,mBAAmB,QAAQ;AAAA,KAC7B;AAAA;AACF,EAEA,MAAM,WAAW,QAAmC,EAAA;AAClD,IAAO,OAAA,IAAA,CAAK,mBAAoB,CAAA,QAAA,EAAU,UAAU,CAAA;AAAA;AACtD,EAEA,MAAM,mBAAmB,QAAmC,EAAA;AAC1D,IAAO,OAAA,IAAA,CAAK,mBAAoB,CAAA,QAAA,EAAU,UAAU,CAAA;AAAA;AAExD;;;;"}
1
+ {"version":3,"file":"HostDiscovery.cjs.js","sources":["../../../src/entrypoints/discovery/HostDiscovery.ts"],"sourcesContent":["/*\n * Copyright 2020 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 DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { readHttpServerOptions } from '../rootHttpRouter/http/config';\nimport { SrvResolvers } from './SrvResolvers';\nimport { trimEnd } from 'lodash';\n\ntype Resolver = (pluginId: string) => Promise<string>;\n\n/**\n * A list of target base URLs and their associated plugins.\n *\n * @public\n */\nexport interface HostDiscoveryEndpoint {\n /**\n * The target base URL to use for the given set of plugins. Note that this\n * needs to be a full URL _including_ the protocol and path parts that fully\n * address the root of a plugin's API endpoints.\n *\n * @remarks\n *\n * Can be either a single URL or an object where you can explicitly give a\n * dedicated URL for internal (as seen from the backend) and/or external (as\n * seen from the frontend) lookups.\n *\n * The default behavior is to use the backend base URL for external lookups,\n * and a URL formed from the `.listen` and `.https` configs for internal\n * lookups. Adding discovery endpoints as described here overrides one or both\n * of those behaviors for a given set of plugins.\n *\n * URLs can be in the form of a regular HTTP or HTTPS URL if you are using\n * A/AAAA/CNAME records or IP addresses. Specifically for internal URLs, if\n * you add `+src` to the protocol part then the hostname is treated as an SRV\n * record name and resolved. For example, if you pass in\n * `http+srv://<record>/path` then the record part is resolved into an\n * actual host and port (with random weighted choice as usual when there is\n * more than one match).\n *\n * Any strings with `{{pluginId}}` or `{{ pluginId }}` placeholders in them\n * will have them replaced with the plugin ID.\n *\n * Example URLs:\n *\n * - `https://internal.example.com/secure/api/{{ pluginId }}`\n * - `http+srv://backstage-plugin-{{pluginId}}.http.services.company.net/api/{{pluginId}}`\n * (can only be used in the `internal` key)\n */\n target:\n | string\n | {\n internal?: string;\n external?: string;\n };\n\n /**\n * Array of plugins which use that target base URL.\n *\n * The special value `*` can be used to match all plugins.\n */\n plugins: string[];\n}\n\n/**\n * Options for the {@link HostDiscovery} class.\n *\n * @public\n */\nexport interface HostDiscoveryOptions {\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * A default set of endpoints to use.\n *\n * @remarks\n *\n * These endpoints have lower priority than any that are defined in\n * app-config, but higher priority than the fallback ones.\n *\n * This parameter is usedful for example if you want to provide a shared\n * library of core services to your plugin developers, which is set up for the\n * default behaviors in your org. This alleviates the need for replicating any\n * given set of endpoint config in every backend that you deploy.\n */\n defaultEndpoints?: HostDiscoveryEndpoint[];\n}\n\n/**\n * A basic {@link @backstage/backend-plugin-api#DiscoveryService} implementation\n * that can handle plugins that are hosted in a single or multiple deployments.\n *\n * @public\n * @remarks\n *\n * Configuration is read from the `backend` config section, specifically the\n * `.baseUrl` for discovering the external URL, and the `.listen` and `.https`\n * config for the internal one. The fixed base path for these is `/api`, meaning\n * for example the default full internal path for the `catalog` plugin typically\n * will be `http://localhost:7007/api/catalog`.\n *\n * Those defaults can be overridden by providing a target and corresponding\n * plugins in `discovery.endpoints`, e.g.:\n *\n * ```yaml\n * discovery:\n * endpoints:\n * # Set a static internal and external base URL for a plugin\n * - target: https://internal.example.com/internal-catalog\n * plugins: [catalog]\n * # Sets a dynamic internal and external base URL pattern for two plugins\n * - target: https://internal.example.com/secure/api/{{pluginId}}\n * plugins: [auth, permission]\n * # Sets a dynamic base URL pattern for only the internal resolution for all\n * # other plugins, while leaving the external resolution unaffected\n * - target:\n * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}}\n * plugins: [*]\n * ```\n */\nexport class HostDiscovery implements DiscoveryService {\n #srvResolver: SrvResolvers;\n #internalResolvers: Map<string, Resolver> = new Map();\n #externalResolvers: Map<string, Resolver> = new Map();\n #internalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n #externalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n\n static fromConfig(config: RootConfigService, options?: HostDiscoveryOptions) {\n const discovery = new HostDiscovery(new SrvResolvers());\n\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n config.subscribe?.(() => {\n try {\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n } catch (e) {\n options?.logger.error(`Failed to update discovery service: ${e}`);\n }\n });\n\n return discovery;\n }\n\n private constructor(srvResolver: SrvResolvers) {\n this.#srvResolver = srvResolver;\n this.#internalResolvers = new Map();\n this.#externalResolvers = new Map();\n this.#internalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n this.#externalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#internalResolvers.get(pluginId) ??\n this.#internalResolvers.get('*') ??\n this.#internalFallbackResolver;\n return await resolver(pluginId);\n }\n\n async getExternalBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#externalResolvers.get(pluginId) ??\n this.#externalResolvers.get('*') ??\n this.#externalFallbackResolver;\n return await resolver(pluginId);\n }\n\n #updateResolvers(config: Config, defaultEndpoints?: HostDiscoveryEndpoint[]) {\n this.#updateFallbackResolvers(config);\n this.#updatePluginResolvers(config, defaultEndpoints);\n }\n\n #updateFallbackResolvers(config: Config) {\n const backendBaseUrl = trimEnd(config.getString('backend.baseUrl'), '/');\n\n const {\n listen: { host: listenHost = '::', port: listenPort },\n } = readHttpServerOptions(config.getConfig('backend'));\n const protocol = config.has('backend.https') ? 'https' : 'http';\n\n // Translate bind-all to localhost, and support IPv6\n let host = listenHost;\n if (host === '::' || host === '') {\n // We use localhost instead of ::1, since IPv6-compatible systems should default\n // to using IPv6 when they see localhost, but if the system doesn't support IPv6\n // things will still work.\n host = 'localhost';\n } else if (host === '0.0.0.0') {\n host = '127.0.0.1';\n }\n if (host.includes(':')) {\n host = `[${host}]`;\n }\n\n this.#internalFallbackResolver = this.#makeResolver(\n `${protocol}://${host}:${listenPort}/api/{{pluginId}}`,\n false,\n );\n this.#externalFallbackResolver = this.#makeResolver(\n `${backendBaseUrl}/api/{{pluginId}}`,\n false,\n );\n }\n\n #updatePluginResolvers(\n config: Config,\n defaultEndpoints?: HostDiscoveryEndpoint[],\n ) {\n // Start out with the default endpoints, if any\n const endpoints = defaultEndpoints?.slice() ?? [];\n\n // Allow config to override the default endpoints\n const endpointConfigs = config.getOptionalConfigArray(\n 'discovery.endpoints',\n );\n for (const endpointConfig of endpointConfigs ?? []) {\n if (typeof endpointConfig.get('target') === 'string') {\n endpoints.push({\n target: endpointConfig.getString('target'),\n plugins: endpointConfig.getStringArray('plugins'),\n });\n } else {\n endpoints.push({\n target: {\n internal: endpointConfig.getOptionalString('target.internal'),\n external: endpointConfig.getOptionalString('target.external'),\n },\n plugins: endpointConfig.getStringArray('plugins'),\n });\n }\n }\n\n // Build up a new set of resolvers\n const internalResolvers: Map<string, Resolver> = new Map();\n const externalResolvers: Map<string, Resolver> = new Map();\n for (const { target, plugins } of endpoints) {\n let internalResolver: Resolver | undefined;\n let externalResolver: Resolver | undefined;\n\n if (typeof target === 'string') {\n internalResolver = externalResolver = this.#makeResolver(target, false);\n } else {\n if (target.internal) {\n internalResolver = this.#makeResolver(target.internal, true);\n }\n if (target.external) {\n externalResolver = this.#makeResolver(target.external, false);\n }\n }\n\n if (internalResolver) {\n for (const pluginId of plugins) {\n internalResolvers.set(pluginId, internalResolver);\n }\n }\n if (externalResolver) {\n for (const pluginId of plugins) {\n externalResolvers.set(pluginId, externalResolver);\n }\n }\n }\n\n // Only persist if no errors were thrown above\n this.#internalResolvers = internalResolvers;\n this.#externalResolvers = externalResolvers;\n }\n\n #makeResolver(urlPattern: string, allowSrv: boolean): Resolver {\n const withPluginId = (pluginId: string, url: string) => {\n return url.replace(\n /\\{\\{\\s*pluginId\\s*\\}\\}/g,\n encodeURIComponent(pluginId),\n );\n };\n\n if (!this.#srvResolver.isSrvUrl(urlPattern)) {\n return async pluginId => withPluginId(pluginId, urlPattern);\n }\n\n if (!allowSrv) {\n throw new Error(\n `SRV resolver URLs cannot be used in the target for external endpoints`,\n );\n }\n\n const lazyResolvers = new Map<string, () => Promise<string>>();\n return async pluginId => {\n let lazyResolver = lazyResolvers.get(pluginId);\n if (!lazyResolver) {\n lazyResolver = this.#srvResolver.getResolver(\n withPluginId(pluginId, urlPattern),\n );\n lazyResolvers.set(pluginId, lazyResolver);\n }\n return await lazyResolver();\n };\n }\n}\n"],"names":["SrvResolvers","config","trimEnd","readHttpServerOptions"],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var dns = require('dns');
5
+
6
+ const PROTOCOL_SUFFIX = "+srv:";
7
+ class SrvResolvers {
8
+ #cache;
9
+ #cacheTtlMillis;
10
+ #resolveSrv;
11
+ constructor(options) {
12
+ this.#cache = /* @__PURE__ */ new Map();
13
+ this.#cacheTtlMillis = options?.cacheTtlMillis ?? 1e3;
14
+ this.#resolveSrv = options?.resolveSrv ?? ((host) => new Promise((resolve, reject) => {
15
+ dns.resolveSrv(host, (err, result) => {
16
+ if (err) {
17
+ reject(err);
18
+ } else {
19
+ resolve(result);
20
+ }
21
+ });
22
+ }));
23
+ }
24
+ isSrvUrl(url) {
25
+ try {
26
+ this.#parseSrvUrl(url);
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Get a resolver function for a given SRV form URL.
34
+ *
35
+ * @param url An SRV form URL, e.g. `http+srv://myplugin.services.region.example.net/api/myplugin`
36
+ * @returns A function that returns resolved URLs, e.g. `http://1234abcd.region.example.net:8080/api/myplugin`
37
+ */
38
+ getResolver(url) {
39
+ const { protocol, host, path } = this.#parseSrvUrl(url);
40
+ return () => this.#resolveHost(host).then(
41
+ (resolved) => `${protocol}://${resolved}${path}`
42
+ );
43
+ }
44
+ /**
45
+ * Attempts to parse out the relevant parts of an SRV URL.
46
+ */
47
+ #parseSrvUrl(url) {
48
+ let parsedUrl;
49
+ try {
50
+ parsedUrl = new URL(url);
51
+ } catch {
52
+ throw new errors.InputError(
53
+ `SRV resolver expected a valid URL starting with http(s)+srv:// but got '${url}'`
54
+ );
55
+ }
56
+ if (!parsedUrl.protocol?.endsWith(PROTOCOL_SUFFIX) || !parsedUrl.hostname) {
57
+ throw new errors.InputError(
58
+ `SRV resolver expected a URL with protocol http(s)+srv:// but got '${url}'`
59
+ );
60
+ }
61
+ if (parsedUrl.port) {
62
+ throw new errors.InputError(
63
+ `SRV resolver URLs cannot contain a port but got '${url}'`
64
+ );
65
+ }
66
+ if (parsedUrl.username || parsedUrl.password) {
67
+ throw new errors.InputError(
68
+ `SRV resolver URLs cannot contain username or password but got '${url}'`
69
+ );
70
+ }
71
+ if (parsedUrl.search || parsedUrl.hash) {
72
+ throw new errors.InputError(
73
+ `SRV resolver URLs cannot contain search params or a hash but got '${url}'`
74
+ );
75
+ }
76
+ const protocol = parsedUrl.protocol.substring(
77
+ 0,
78
+ parsedUrl.protocol.length - PROTOCOL_SUFFIX.length
79
+ );
80
+ const host = parsedUrl.hostname;
81
+ const path = parsedUrl.pathname.replace(/\/+$/, "");
82
+ if (!["http", "https"].includes(protocol)) {
83
+ throw new errors.InputError(
84
+ `SRV URLs must be based on http or https but got '${url}'`
85
+ );
86
+ }
87
+ return { protocol, host, path };
88
+ }
89
+ /**
90
+ * Resolves a single SRV record name to a host:port string.
91
+ */
92
+ #resolveHost(host) {
93
+ let records = this.#cache.get(host);
94
+ if (!records) {
95
+ records = this.#resolveSrv(host).then(
96
+ (result) => {
97
+ if (!result.length) {
98
+ throw new errors.NotFoundError(`No SRV records found for ${host}`);
99
+ }
100
+ return result;
101
+ },
102
+ (err) => {
103
+ throw new errors.ForwardedError(`Failed SRV resolution for ${host}`, err);
104
+ }
105
+ );
106
+ this.#cache.set(host, records);
107
+ setTimeout(() => {
108
+ this.#cache.delete(host);
109
+ }, this.#cacheTtlMillis);
110
+ }
111
+ return records.then((rs) => {
112
+ const r = this.#pickRandomRecord(rs);
113
+ return `${r.name}:${r.port}`;
114
+ });
115
+ }
116
+ /**
117
+ * Among a set of records, pick one at random.
118
+ *
119
+ * This assumes that the set is not empty.
120
+ *
121
+ * Since this contract only ever returns a single record, the best it can do
122
+ * is to pick weighted-randomly among the highest-priority records. In order
123
+ * to be smarter than that, the caller would have to be able to make decisions
124
+ * on the whole set of records.
125
+ */
126
+ #pickRandomRecord(allRecords) {
127
+ const lowestPriority = allRecords.reduce(
128
+ (acc, r) => Math.min(acc, r.priority),
129
+ Number.MAX_SAFE_INTEGER
130
+ );
131
+ const records = allRecords.filter((r) => r.priority === lowestPriority);
132
+ const totalWeight = records.reduce((acc, r) => acc + r.weight, 0);
133
+ const targetWeight = Math.random() * totalWeight;
134
+ let result = records[0];
135
+ let currentWeight = 0;
136
+ for (const record of records) {
137
+ currentWeight += record.weight;
138
+ if (targetWeight <= currentWeight) {
139
+ result = record;
140
+ break;
141
+ }
142
+ }
143
+ return result;
144
+ }
145
+ }
146
+
147
+ exports.SrvResolvers = SrvResolvers;
148
+ //# sourceMappingURL=SrvResolvers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SrvResolvers.cjs.js","sources":["../../../src/entrypoints/discovery/SrvResolvers.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { ForwardedError, InputError, NotFoundError } from '@backstage/errors';\nimport { resolveSrv, SrvRecord } from 'dns';\n\nconst PROTOCOL_SUFFIX = '+srv:';\n\n/**\n * Helps with resolution and caching of SRV lookups.\n *\n * Supports URLs on the form `http+srv://myplugin.services.region.example.net/api/myplugin`\n */\nexport class SrvResolvers {\n readonly #cache: Map<string, Promise<SrvRecord[]>>;\n readonly #cacheTtlMillis: number;\n readonly #resolveSrv: (host: string) => Promise<SrvRecord[]>;\n\n constructor(options?: {\n resolveSrv?: (host: string) => Promise<SrvRecord[]>;\n cacheTtlMillis?: number;\n }) {\n this.#cache = new Map();\n this.#cacheTtlMillis = options?.cacheTtlMillis ?? 1000;\n this.#resolveSrv =\n options?.resolveSrv ??\n (host =>\n new Promise((resolve, reject) => {\n resolveSrv(host, (err, result) => {\n if (err) {\n reject(err);\n } else {\n resolve(result);\n }\n });\n }));\n }\n\n isSrvUrl(url: string): boolean {\n try {\n this.#parseSrvUrl(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get a resolver function for a given SRV form URL.\n *\n * @param url An SRV form URL, e.g. `http+srv://myplugin.services.region.example.net/api/myplugin`\n * @returns A function that returns resolved URLs, e.g. `http://1234abcd.region.example.net:8080/api/myplugin`\n */\n getResolver(url: string): () => Promise<string> {\n const { protocol, host, path } = this.#parseSrvUrl(url);\n return () =>\n this.#resolveHost(host).then(\n resolved => `${protocol}://${resolved}${path}`,\n );\n }\n\n /**\n * Attempts to parse out the relevant parts of an SRV URL.\n */\n #parseSrvUrl(url: string): { protocol: string; host: string; path: string } {\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n throw new InputError(\n `SRV resolver expected a valid URL starting with http(s)+srv:// but got '${url}'`,\n );\n }\n if (!parsedUrl.protocol?.endsWith(PROTOCOL_SUFFIX) || !parsedUrl.hostname) {\n throw new InputError(\n `SRV resolver expected a URL with protocol http(s)+srv:// but got '${url}'`,\n );\n }\n if (parsedUrl.port) {\n throw new InputError(\n `SRV resolver URLs cannot contain a port but got '${url}'`,\n );\n }\n if (parsedUrl.username || parsedUrl.password) {\n throw new InputError(\n `SRV resolver URLs cannot contain username or password but got '${url}'`,\n );\n }\n if (parsedUrl.search || parsedUrl.hash) {\n throw new InputError(\n `SRV resolver URLs cannot contain search params or a hash but got '${url}'`,\n );\n }\n\n const protocol = parsedUrl.protocol.substring(\n 0,\n parsedUrl.protocol.length - PROTOCOL_SUFFIX.length,\n );\n const host = parsedUrl.hostname;\n const path = parsedUrl.pathname.replace(/\\/+$/, '');\n\n if (!['http', 'https'].includes(protocol)) {\n throw new InputError(\n `SRV URLs must be based on http or https but got '${url}'`,\n );\n }\n\n return { protocol, host, path };\n }\n\n /**\n * Resolves a single SRV record name to a host:port string.\n */\n #resolveHost(host: string): Promise<string> {\n let records = this.#cache.get(host);\n if (!records) {\n records = this.#resolveSrv(host).then(\n result => {\n if (!result.length) {\n throw new NotFoundError(`No SRV records found for ${host}`);\n }\n return result;\n },\n err => {\n throw new ForwardedError(`Failed SRV resolution for ${host}`, err);\n },\n );\n this.#cache.set(host, records);\n setTimeout(() => {\n this.#cache.delete(host);\n }, this.#cacheTtlMillis);\n }\n\n return records.then(rs => {\n const r = this.#pickRandomRecord(rs);\n return `${r.name}:${r.port}`;\n });\n }\n\n /**\n * Among a set of records, pick one at random.\n *\n * This assumes that the set is not empty.\n *\n * Since this contract only ever returns a single record, the best it can do\n * is to pick weighted-randomly among the highest-priority records. In order\n * to be smarter than that, the caller would have to be able to make decisions\n * on the whole set of records.\n */\n #pickRandomRecord(allRecords: SrvRecord[]): SrvRecord {\n // Lowest priority number means highest priority\n const lowestPriority = allRecords.reduce(\n (acc, r) => Math.min(acc, r.priority),\n Number.MAX_SAFE_INTEGER,\n );\n const records = allRecords.filter(r => r.priority === lowestPriority);\n\n const totalWeight = records.reduce((acc, r) => acc + r.weight, 0);\n const targetWeight = Math.random() * totalWeight;\n\n // Just as a fallback, we expect the loop below to always find a result\n let result = records[0];\n let currentWeight = 0;\n\n for (const record of records) {\n currentWeight += record.weight;\n if (targetWeight <= currentWeight) {\n result = record;\n break;\n }\n }\n\n return result;\n }\n}\n"],"names":["resolveSrv","InputError","NotFoundError","ForwardedError"],"mappings":"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"}
@@ -6,10 +6,14 @@ var HostDiscovery = require('./HostDiscovery.cjs.js');
6
6
  const discoveryServiceFactory = backendPluginApi.createServiceFactory({
7
7
  service: backendPluginApi.coreServices.discovery,
8
8
  deps: {
9
- config: backendPluginApi.coreServices.rootConfig
9
+ config: backendPluginApi.coreServices.rootConfig,
10
+ logger: backendPluginApi.coreServices.logger
10
11
  },
11
- async factory({ config }) {
12
- return HostDiscovery.HostDiscovery.fromConfig(config);
12
+ async factory({ config, logger }) {
13
+ return HostDiscovery.HostDiscovery.fromConfig(config, {
14
+ logger,
15
+ defaultEndpoints: []
16
+ });
13
17
  }
14
18
  });
15
19
 
@@ -1 +1 @@
1
- {"version":3,"file":"discoveryServiceFactory.cjs.js","sources":["../../../src/entrypoints/discovery/discoveryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 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 {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { HostDiscovery } from './HostDiscovery';\n\n/**\n * Service discovery for inter-plugin communication.\n *\n * See {@link @backstage/code-plugin-api#DiscoveryService}\n * and {@link https://backstage.io/docs/backend-system/core-services/discovery | the service docs}\n * for more information.\n *\n * @public\n */\nexport const discoveryServiceFactory = createServiceFactory({\n service: coreServices.discovery,\n deps: {\n config: coreServices.rootConfig,\n },\n async factory({ config }) {\n return HostDiscovery.fromConfig(config);\n },\n});\n"],"names":["createServiceFactory","coreServices","HostDiscovery"],"mappings":";;;;;AA+BO,MAAM,0BAA0BA,qCAAqB,CAAA;AAAA,EAC1D,SAASC,6BAAa,CAAA,SAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,MAAA,EAAU,EAAA;AACxB,IAAO,OAAAC,2BAAA,CAAc,WAAW,MAAM,CAAA;AAAA;AAE1C,CAAC;;;;"}
1
+ {"version":3,"file":"discoveryServiceFactory.cjs.js","sources":["../../../src/entrypoints/discovery/discoveryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 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 {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { HostDiscovery } from './HostDiscovery';\n\n/**\n * Service discovery for inter-plugin communication.\n *\n * See {@link @backstage/code-plugin-api#DiscoveryService}\n * and {@link https://backstage.io/docs/backend-system/core-services/discovery | the service docs}\n * for more information.\n *\n * @public\n */\nexport const discoveryServiceFactory = createServiceFactory({\n service: coreServices.discovery,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n },\n async factory({ config, logger }) {\n return HostDiscovery.fromConfig(config, {\n logger,\n defaultEndpoints: [],\n });\n },\n});\n"],"names":["createServiceFactory","coreServices","HostDiscovery"],"mappings":";;;;;AA+BO,MAAM,0BAA0BA,qCAAqB,CAAA;AAAA,EAC1D,SAASC,6BAAa,CAAA,SAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,MAAA,EAAQ,QAAU,EAAA;AAChC,IAAO,OAAAC,2BAAA,CAAc,WAAW,MAAQ,EAAA;AAAA,MACtC,MAAA;AAAA,MACA,kBAAkB;AAAC,KACpB,CAAA;AAAA;AAEL,CAAC;;;;"}
@@ -2,6 +2,12 @@
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
4
  var pluginPermissionNode = require('@backstage/plugin-permission-node');
5
+ var errors = require('@backstage/errors');
6
+ var Router = require('express-promise-router');
7
+
8
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
9
+
10
+ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
5
11
 
6
12
  function assertRefPluginId(ref, pluginId) {
7
13
  if (ref.pluginId !== pluginId) {
@@ -13,13 +19,29 @@ function assertRefPluginId(ref, pluginId) {
13
19
  const permissionsRegistryServiceFactory = backendPluginApi.createServiceFactory({
14
20
  service: backendPluginApi.coreServices.permissionsRegistry,
15
21
  deps: {
22
+ auth: backendPluginApi.coreServices.auth,
23
+ httpAuth: backendPluginApi.coreServices.httpAuth,
16
24
  lifecycle: backendPluginApi.coreServices.lifecycle,
17
25
  httpRouter: backendPluginApi.coreServices.httpRouter,
18
26
  pluginMetadata: backendPluginApi.coreServices.pluginMetadata
19
27
  },
20
- async factory({ httpRouter, lifecycle, pluginMetadata }) {
28
+ async factory({ auth, httpAuth, httpRouter, lifecycle, pluginMetadata }) {
21
29
  const router = pluginPermissionNode.createPermissionIntegrationRouter();
22
30
  const pluginId = pluginMetadata.getId();
31
+ const applyConditionMiddleware = Router__default.default();
32
+ applyConditionMiddleware.use(
33
+ "/.well-known/backstage/permissions/apply-conditions",
34
+ async (req, _res, next) => {
35
+ const credentials = await httpAuth.credentials(req, {
36
+ allow: ["user", "service"]
37
+ });
38
+ if (auth.isPrincipal(credentials, "user") && !credentials.principal.actor) {
39
+ throw new errors.NotAllowedError();
40
+ }
41
+ next();
42
+ }
43
+ );
44
+ httpRouter.use(applyConditionMiddleware);
23
45
  httpRouter.use(router);
24
46
  let started = false;
25
47
  lifecycle.addStartupHook(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"permissionsRegistryServiceFactory.cjs.js","sources":["../../../src/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 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 {\n PermissionsRegistryService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport {\n PermissionResourceRef,\n createPermissionIntegrationRouter,\n} from '@backstage/plugin-permission-node';\n\nfunction assertRefPluginId(ref: PermissionResourceRef, pluginId: string) {\n if (ref.pluginId !== pluginId) {\n throw new Error(\n `Resource type '${ref.resourceType}' belongs to plugin '${ref.pluginId}', but was used with plugin '${pluginId}'`,\n );\n }\n}\n\n/**\n * Permission system integration for registering resources and permissions.\n *\n * See {@link @backstage/core-plugin-api#PermissionsRegistryService}\n * and {@link https://backstage.io/docs/backend-system/core-services/permission-integrations | the service docs}\n * for more information.\n *\n * @public\n */\nexport const permissionsRegistryServiceFactory = createServiceFactory({\n service: coreServices.permissionsRegistry,\n deps: {\n lifecycle: coreServices.lifecycle,\n httpRouter: coreServices.httpRouter,\n pluginMetadata: coreServices.pluginMetadata,\n },\n async factory({ httpRouter, lifecycle, pluginMetadata }) {\n const router = createPermissionIntegrationRouter();\n const pluginId = pluginMetadata.getId();\n\n httpRouter.use(router);\n\n let started = false;\n lifecycle.addStartupHook(() => {\n started = true;\n });\n\n return {\n addResourceType(resource) {\n if (started) {\n throw new Error(\n 'Cannot add permission resource types after the plugin has started',\n );\n }\n assertRefPluginId(resource.resourceRef, pluginId);\n router.addResourceType({\n ...resource,\n resourceType: resource.resourceRef.resourceType,\n });\n },\n addPermissions(permissions) {\n if (started) {\n throw new Error(\n 'Cannot add permissions after the plugin has started',\n );\n }\n router.addPermissions(permissions);\n },\n addPermissionRules(rules) {\n if (started) {\n throw new Error(\n 'Cannot add permission rules after the plugin has started',\n );\n }\n router.addPermissionRules(rules);\n },\n getPermissionRuleset(resourceRef) {\n assertRefPluginId(resourceRef, pluginId);\n return router.getPermissionRuleset(resourceRef);\n },\n } satisfies PermissionsRegistryService;\n },\n});\n"],"names":["createServiceFactory","coreServices","createPermissionIntegrationRouter"],"mappings":";;;;;AA0BA,SAAS,iBAAA,CAAkB,KAA4B,QAAkB,EAAA;AACvE,EAAI,IAAA,GAAA,CAAI,aAAa,QAAU,EAAA;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,kBAAkB,GAAI,CAAA,YAAY,wBAAwB,GAAI,CAAA,QAAQ,gCAAgC,QAAQ,CAAA,CAAA;AAAA,KAChH;AAAA;AAEJ;AAWO,MAAM,oCAAoCA,qCAAqB,CAAA;AAAA,EACpE,SAASC,6BAAa,CAAA,mBAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,YAAYA,6BAAa,CAAA,UAAA;AAAA,IACzB,gBAAgBA,6BAAa,CAAA;AAAA,GAC/B;AAAA,EACA,MAAM,OAAQ,CAAA,EAAE,UAAY,EAAA,SAAA,EAAW,gBAAkB,EAAA;AACvD,IAAA,MAAM,SAASC,sDAAkC,EAAA;AACjD,IAAM,MAAA,QAAA,GAAW,eAAe,KAAM,EAAA;AAEtC,IAAA,UAAA,CAAW,IAAI,MAAM,CAAA;AAErB,IAAA,IAAI,OAAU,GAAA,KAAA;AACd,IAAA,SAAA,CAAU,eAAe,MAAM;AAC7B,MAAU,OAAA,GAAA,IAAA;AAAA,KACX,CAAA;AAED,IAAO,OAAA;AAAA,MACL,gBAAgB,QAAU,EAAA;AACxB,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAkB,iBAAA,CAAA,QAAA,CAAS,aAAa,QAAQ,CAAA;AAChD,QAAA,MAAA,CAAO,eAAgB,CAAA;AAAA,UACrB,GAAG,QAAA;AAAA,UACH,YAAA,EAAc,SAAS,WAAY,CAAA;AAAA,SACpC,CAAA;AAAA,OACH;AAAA,MACA,eAAe,WAAa,EAAA;AAC1B,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,MAAA,CAAO,eAAe,WAAW,CAAA;AAAA,OACnC;AAAA,MACA,mBAAmB,KAAO,EAAA;AACxB,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,MAAA,CAAO,mBAAmB,KAAK,CAAA;AAAA,OACjC;AAAA,MACA,qBAAqB,WAAa,EAAA;AAChC,QAAA,iBAAA,CAAkB,aAAa,QAAQ,CAAA;AACvC,QAAO,OAAA,MAAA,CAAO,qBAAqB,WAAW,CAAA;AAAA;AAChD,KACF;AAAA;AAEJ,CAAC;;;;"}
1
+ {"version":3,"file":"permissionsRegistryServiceFactory.cjs.js","sources":["../../../src/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 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 {\n PermissionsRegistryService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport {\n PermissionResourceRef,\n createPermissionIntegrationRouter,\n} from '@backstage/plugin-permission-node';\nimport { NotAllowedError } from '@backstage/errors';\nimport Router from 'express-promise-router';\n\nfunction assertRefPluginId(ref: PermissionResourceRef, pluginId: string) {\n if (ref.pluginId !== pluginId) {\n throw new Error(\n `Resource type '${ref.resourceType}' belongs to plugin '${ref.pluginId}', but was used with plugin '${pluginId}'`,\n );\n }\n}\n\n/**\n * Permission system integration for registering resources and permissions.\n *\n * See {@link @backstage/core-plugin-api#PermissionsRegistryService}\n * and {@link https://backstage.io/docs/backend-system/core-services/permission-integrations | the service docs}\n * for more information.\n *\n * @public\n */\nexport const permissionsRegistryServiceFactory = createServiceFactory({\n service: coreServices.permissionsRegistry,\n deps: {\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n lifecycle: coreServices.lifecycle,\n httpRouter: coreServices.httpRouter,\n pluginMetadata: coreServices.pluginMetadata,\n },\n async factory({ auth, httpAuth, httpRouter, lifecycle, pluginMetadata }) {\n const router = createPermissionIntegrationRouter();\n\n const pluginId = pluginMetadata.getId();\n\n const applyConditionMiddleware = Router();\n applyConditionMiddleware.use(\n '/.well-known/backstage/permissions/apply-conditions',\n async (req, _res, next) => {\n const credentials = await httpAuth.credentials(req, {\n allow: ['user', 'service'],\n });\n if (\n auth.isPrincipal(credentials, 'user') &&\n !credentials.principal.actor\n ) {\n throw new NotAllowedError();\n }\n next();\n },\n );\n httpRouter.use(applyConditionMiddleware);\n httpRouter.use(router);\n\n let started = false;\n lifecycle.addStartupHook(() => {\n started = true;\n });\n\n return {\n addResourceType(resource) {\n if (started) {\n throw new Error(\n 'Cannot add permission resource types after the plugin has started',\n );\n }\n assertRefPluginId(resource.resourceRef, pluginId);\n router.addResourceType({\n ...resource,\n resourceType: resource.resourceRef.resourceType,\n });\n },\n addPermissions(permissions) {\n if (started) {\n throw new Error(\n 'Cannot add permissions after the plugin has started',\n );\n }\n router.addPermissions(permissions);\n },\n addPermissionRules(rules) {\n if (started) {\n throw new Error(\n 'Cannot add permission rules after the plugin has started',\n );\n }\n router.addPermissionRules(rules);\n },\n getPermissionRuleset(resourceRef) {\n assertRefPluginId(resourceRef, pluginId);\n return router.getPermissionRuleset(resourceRef);\n },\n } satisfies PermissionsRegistryService;\n },\n});\n"],"names":["createServiceFactory","coreServices","createPermissionIntegrationRouter","Router","NotAllowedError"],"mappings":";;;;;;;;;;;AA4BA,SAAS,iBAAA,CAAkB,KAA4B,QAAkB,EAAA;AACvE,EAAI,IAAA,GAAA,CAAI,aAAa,QAAU,EAAA;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,kBAAkB,GAAI,CAAA,YAAY,wBAAwB,GAAI,CAAA,QAAQ,gCAAgC,QAAQ,CAAA,CAAA;AAAA,KAChH;AAAA;AAEJ;AAWO,MAAM,oCAAoCA,qCAAqB,CAAA;AAAA,EACpE,SAASC,6BAAa,CAAA,mBAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,MAAMA,6BAAa,CAAA,IAAA;AAAA,IACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,IACvB,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,YAAYA,6BAAa,CAAA,UAAA;AAAA,IACzB,gBAAgBA,6BAAa,CAAA;AAAA,GAC/B;AAAA,EACA,MAAM,QAAQ,EAAE,IAAA,EAAM,UAAU,UAAY,EAAA,SAAA,EAAW,gBAAkB,EAAA;AACvE,IAAA,MAAM,SAASC,sDAAkC,EAAA;AAEjD,IAAM,MAAA,QAAA,GAAW,eAAe,KAAM,EAAA;AAEtC,IAAA,MAAM,2BAA2BC,uBAAO,EAAA;AACxC,IAAyB,wBAAA,CAAA,GAAA;AAAA,MACvB,qDAAA;AAAA,MACA,OAAO,GAAK,EAAA,IAAA,EAAM,IAAS,KAAA;AACzB,QAAA,MAAM,WAAc,GAAA,MAAM,QAAS,CAAA,WAAA,CAAY,GAAK,EAAA;AAAA,UAClD,KAAA,EAAO,CAAC,MAAA,EAAQ,SAAS;AAAA,SAC1B,CAAA;AACD,QACE,IAAA,IAAA,CAAK,YAAY,WAAa,EAAA,MAAM,KACpC,CAAC,WAAA,CAAY,UAAU,KACvB,EAAA;AACA,UAAA,MAAM,IAAIC,sBAAgB,EAAA;AAAA;AAE5B,QAAK,IAAA,EAAA;AAAA;AACP,KACF;AACA,IAAA,UAAA,CAAW,IAAI,wBAAwB,CAAA;AACvC,IAAA,UAAA,CAAW,IAAI,MAAM,CAAA;AAErB,IAAA,IAAI,OAAU,GAAA,KAAA;AACd,IAAA,SAAA,CAAU,eAAe,MAAM;AAC7B,MAAU,OAAA,GAAA,IAAA;AAAA,KACX,CAAA;AAED,IAAO,OAAA;AAAA,MACL,gBAAgB,QAAU,EAAA;AACxB,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAkB,iBAAA,CAAA,QAAA,CAAS,aAAa,QAAQ,CAAA;AAChD,QAAA,MAAA,CAAO,eAAgB,CAAA;AAAA,UACrB,GAAG,QAAA;AAAA,UACH,YAAA,EAAc,SAAS,WAAY,CAAA;AAAA,SACpC,CAAA;AAAA,OACH;AAAA,MACA,eAAe,WAAa,EAAA;AAC1B,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,MAAA,CAAO,eAAe,WAAW,CAAA;AAAA,OACnC;AAAA,MACA,mBAAmB,KAAO,EAAA;AACxB,QAAA,IAAI,OAAS,EAAA;AACX,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,MAAA,CAAO,mBAAmB,KAAK,CAAA;AAAA,OACjC;AAAA,MACA,qBAAqB,WAAa,EAAA;AAChC,QAAA,iBAAA,CAAkB,aAAa,QAAQ,CAAA;AACvC,QAAO,OAAA,MAAA,CAAO,qBAAqB,WAAW,CAAA;AAAA;AAChD,KACF;AAAA;AAEJ,CAAC;;;;"}
@@ -32,6 +32,7 @@ const rootHttpRouterServiceFactoryWithOptions = (options) => backendPluginApi.cr
32
32
  const { indexPath, configure = defaultConfigure } = options ?? {};
33
33
  const logger = rootLogger.child({ service: "rootHttpRouter" });
34
34
  const app = express__default.default();
35
+ const trustProxy = config$2.getOptional("backend.trustProxy");
35
36
  const router = DefaultRootHttpRouter.DefaultRootHttpRouter.create({ indexPath });
36
37
  const middleware = MiddlewareFactory.MiddlewareFactory.create({ config: config$2, logger });
37
38
  const routes = router.handler();
@@ -54,6 +55,9 @@ const rootHttpRouterServiceFactoryWithOptions = (options) => backendPluginApi.cr
54
55
  if (process.env.NODE_ENV === "development") {
55
56
  app.set("json spaces", 2);
56
57
  }
58
+ if (trustProxy !== void 0) {
59
+ app.set("trust proxy", trustProxy);
60
+ }
57
61
  app.use(middleware.helmet());
58
62
  app.use(middleware.cors());
59
63
  app.use(middleware.compression());
@@ -1 +1 @@
1
- {"version":3,"file":"rootHttpRouterServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 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 {\n RootConfigService,\n coreServices,\n createServiceFactory,\n LifecycleService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport express, { RequestHandler, Express } from 'express';\nimport type { Server } from 'node:http';\nimport {\n createHttpServer,\n MiddlewareFactory,\n readHttpServerOptions,\n} from './http';\nimport { DefaultRootHttpRouter } from './DefaultRootHttpRouter';\nimport { createHealthRouter } from './createHealthRouter';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { readDurationFromConfig } from '@backstage/config';\n\n/**\n * @public\n */\nexport interface RootHttpRouterConfigureContext {\n app: Express;\n server: Server;\n middleware: MiddlewareFactory;\n routes: RequestHandler;\n config: RootConfigService;\n logger: LoggerService;\n lifecycle: LifecycleService;\n healthRouter: RequestHandler;\n applyDefaults: () => void;\n}\n\n/**\n * HTTP route registration for root services.\n *\n * See {@link @backstage/code-plugin-api#RootHttpRouterService}\n * and {@link https://backstage.io/docs/backend-system/core-services/root-http-router | the service docs}\n * for more information.\n *\n * @public\n */\nexport type RootHttpRouterFactoryOptions = {\n /**\n * The path to forward all unmatched requests to. Defaults to '/api/app' if\n * not given. Disables index path behavior if false is given.\n */\n indexPath?: string | false;\n\n configure?(context: RootHttpRouterConfigureContext): void;\n};\n\nfunction defaultConfigure({ applyDefaults }: RootHttpRouterConfigureContext) {\n applyDefaults();\n}\n\nconst rootHttpRouterServiceFactoryWithOptions = (\n options?: RootHttpRouterFactoryOptions,\n) =>\n createServiceFactory({\n service: coreServices.rootHttpRouter,\n deps: {\n config: coreServices.rootConfig,\n rootLogger: coreServices.rootLogger,\n lifecycle: coreServices.rootLifecycle,\n health: coreServices.rootHealth,\n },\n async factory({ config, rootLogger, lifecycle, health }) {\n const { indexPath, configure = defaultConfigure } = options ?? {};\n const logger = rootLogger.child({ service: 'rootHttpRouter' });\n const app = express();\n\n const router = DefaultRootHttpRouter.create({ indexPath });\n const middleware = MiddlewareFactory.create({ config, logger });\n const routes = router.handler();\n\n const healthRouter = createHealthRouter({ config, health });\n\n const server = await createHttpServer(\n app,\n readHttpServerOptions(config.getOptionalConfig('backend')),\n { logger },\n );\n\n configure({\n app,\n server,\n routes,\n middleware,\n config,\n logger,\n lifecycle,\n healthRouter,\n applyDefaults() {\n if (process.env.NODE_ENV === 'development') {\n app.set('json spaces', 2);\n }\n app.use(middleware.helmet());\n app.use(middleware.cors());\n app.use(middleware.compression());\n app.use(middleware.logging());\n app.use(healthRouter);\n app.use(routes);\n app.use(middleware.notFound());\n app.use(middleware.error());\n },\n });\n\n if (config.has('backend.lifecycle.serverShutdownDelay')) {\n const serverShutdownDelay = readDurationFromConfig(config, {\n key: 'backend.lifecycle.serverShutdownDelay',\n });\n lifecycle.addBeforeShutdownHook(async () => {\n const timeoutMs = durationToMilliseconds(serverShutdownDelay);\n return await new Promise(resolve => {\n setTimeout(resolve, timeoutMs);\n });\n });\n }\n\n lifecycle.addShutdownHook(() => server.stop());\n\n await server.start();\n\n return router;\n },\n });\n\n/** @public */\nexport const rootHttpRouterServiceFactory = Object.assign(\n rootHttpRouterServiceFactoryWithOptions,\n rootHttpRouterServiceFactoryWithOptions(),\n);\n"],"names":["createServiceFactory","coreServices","config","express","DefaultRootHttpRouter","MiddlewareFactory","createHealthRouter","createHttpServer","readHttpServerOptions","readDurationFromConfig","durationToMilliseconds"],"mappings":";;;;;;;;;;;;;;;;;;;AAqEA,SAAS,gBAAA,CAAiB,EAAE,aAAA,EAAiD,EAAA;AAC3E,EAAc,aAAA,EAAA;AAChB;AAEA,MAAM,uCAAA,GAA0C,CAC9C,OAAA,KAEAA,qCAAqB,CAAA;AAAA,EACnB,SAASC,6BAAa,CAAA,cAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,YAAYA,6BAAa,CAAA,UAAA;AAAA,IACzB,WAAWA,6BAAa,CAAA,aAAA;AAAA,IACxB,QAAQA,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,MAAM,OAAQ,CAAA,UAAEC,UAAQ,UAAY,EAAA,SAAA,EAAW,QAAU,EAAA;AACvD,IAAA,MAAM,EAAE,SAAW,EAAA,SAAA,GAAY,gBAAiB,EAAA,GAAI,WAAW,EAAC;AAChE,IAAA,MAAM,SAAS,UAAW,CAAA,KAAA,CAAM,EAAE,OAAA,EAAS,kBAAkB,CAAA;AAC7D,IAAA,MAAM,MAAMC,wBAAQ,EAAA;AAEpB,IAAA,MAAM,MAAS,GAAAC,2CAAA,CAAsB,MAAO,CAAA,EAAE,WAAW,CAAA;AACzD,IAAA,MAAM,aAAaC,mCAAkB,CAAA,MAAA,CAAO,UAAEH,QAAA,EAAQ,QAAQ,CAAA;AAC9D,IAAM,MAAA,MAAA,GAAS,OAAO,OAAQ,EAAA;AAE9B,IAAA,MAAM,YAAe,GAAAI,qCAAA,CAAmB,UAAEJ,QAAA,EAAQ,QAAQ,CAAA;AAE1D,IAAA,MAAM,SAAS,MAAMK,iCAAA;AAAA,MACnB,GAAA;AAAA,MACAC,4BAAsB,CAAAN,QAAA,CAAO,iBAAkB,CAAA,SAAS,CAAC,CAAA;AAAA,MACzD,EAAE,MAAO;AAAA,KACX;AAEA,IAAU,SAAA,CAAA;AAAA,MACR,GAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,UAAA;AAAA,cACAA,QAAA;AAAA,MACA,MAAA;AAAA,MACA,SAAA;AAAA,MACA,YAAA;AAAA,MACA,aAAgB,GAAA;AACd,QAAI,IAAA,OAAA,CAAQ,GAAI,CAAA,QAAA,KAAa,aAAe,EAAA;AAC1C,UAAI,GAAA,CAAA,GAAA,CAAI,eAAe,CAAC,CAAA;AAAA;AAE1B,QAAI,GAAA,CAAA,GAAA,CAAI,UAAW,CAAA,MAAA,EAAQ,CAAA;AAC3B,QAAI,GAAA,CAAA,GAAA,CAAI,UAAW,CAAA,IAAA,EAAM,CAAA;AACzB,QAAI,GAAA,CAAA,GAAA,CAAI,UAAW,CAAA,WAAA,EAAa,CAAA;AAChC,QAAI,GAAA,CAAA,GAAA,CAAI,UAAW,CAAA,OAAA,EAAS,CAAA;AAC5B,QAAA,GAAA,CAAI,IAAI,YAAY,CAAA;AACpB,QAAA,GAAA,CAAI,IAAI,MAAM,CAAA;AACd,QAAI,GAAA,CAAA,GAAA,CAAI,UAAW,CAAA,QAAA,EAAU,CAAA;AAC7B,QAAI,GAAA,CAAA,GAAA,CAAI,UAAW,CAAA,KAAA,EAAO,CAAA;AAAA;AAC5B,KACD,CAAA;AAED,IAAI,IAAAA,QAAA,CAAO,GAAI,CAAA,uCAAuC,CAAG,EAAA;AACvD,MAAM,MAAA,mBAAA,GAAsBO,gCAAuBP,QAAQ,EAAA;AAAA,QACzD,GAAK,EAAA;AAAA,OACN,CAAA;AACD,MAAA,SAAA,CAAU,sBAAsB,YAAY;AAC1C,QAAM,MAAA,SAAA,GAAYQ,6BAAuB,mBAAmB,CAAA;AAC5D,QAAO,OAAA,MAAM,IAAI,OAAA,CAAQ,CAAW,OAAA,KAAA;AAClC,UAAA,UAAA,CAAW,SAAS,SAAS,CAAA;AAAA,SAC9B,CAAA;AAAA,OACF,CAAA;AAAA;AAGH,IAAA,SAAA,CAAU,eAAgB,CAAA,MAAM,MAAO,CAAA,IAAA,EAAM,CAAA;AAE7C,IAAA,MAAM,OAAO,KAAM,EAAA;AAEnB,IAAO,OAAA,MAAA;AAAA;AAEX,CAAC,CAAA;AAGI,MAAM,+BAA+B,MAAO,CAAA,MAAA;AAAA,EACjD,uCAAA;AAAA,EACA,uCAAwC;AAC1C;;;;"}
1
+ {"version":3,"file":"rootHttpRouterServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 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 {\n RootConfigService,\n coreServices,\n createServiceFactory,\n LifecycleService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport express, { RequestHandler, Express } from 'express';\nimport type { Server } from 'node:http';\nimport {\n createHttpServer,\n MiddlewareFactory,\n readHttpServerOptions,\n} from './http';\nimport { DefaultRootHttpRouter } from './DefaultRootHttpRouter';\nimport { createHealthRouter } from './createHealthRouter';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { readDurationFromConfig } from '@backstage/config';\n\n/**\n * @public\n */\nexport interface RootHttpRouterConfigureContext {\n app: Express;\n server: Server;\n middleware: MiddlewareFactory;\n routes: RequestHandler;\n config: RootConfigService;\n logger: LoggerService;\n lifecycle: LifecycleService;\n healthRouter: RequestHandler;\n applyDefaults: () => void;\n}\n\n/**\n * HTTP route registration for root services.\n *\n * See {@link @backstage/code-plugin-api#RootHttpRouterService}\n * and {@link https://backstage.io/docs/backend-system/core-services/root-http-router | the service docs}\n * for more information.\n *\n * @public\n */\nexport type RootHttpRouterFactoryOptions = {\n /**\n * The path to forward all unmatched requests to. Defaults to '/api/app' if\n * not given. Disables index path behavior if false is given.\n */\n indexPath?: string | false;\n\n configure?(context: RootHttpRouterConfigureContext): void;\n};\n\nfunction defaultConfigure({ applyDefaults }: RootHttpRouterConfigureContext) {\n applyDefaults();\n}\n\nconst rootHttpRouterServiceFactoryWithOptions = (\n options?: RootHttpRouterFactoryOptions,\n) =>\n createServiceFactory({\n service: coreServices.rootHttpRouter,\n deps: {\n config: coreServices.rootConfig,\n rootLogger: coreServices.rootLogger,\n lifecycle: coreServices.rootLifecycle,\n health: coreServices.rootHealth,\n },\n async factory({ config, rootLogger, lifecycle, health }) {\n const { indexPath, configure = defaultConfigure } = options ?? {};\n const logger = rootLogger.child({ service: 'rootHttpRouter' });\n const app = express();\n\n const trustProxy = config.getOptional('backend.trustProxy');\n\n const router = DefaultRootHttpRouter.create({ indexPath });\n const middleware = MiddlewareFactory.create({ config, logger });\n const routes = router.handler();\n\n const healthRouter = createHealthRouter({ config, health });\n\n const server = await createHttpServer(\n app,\n readHttpServerOptions(config.getOptionalConfig('backend')),\n { logger },\n );\n\n configure({\n app,\n server,\n routes,\n middleware,\n config,\n logger,\n lifecycle,\n healthRouter,\n applyDefaults() {\n if (process.env.NODE_ENV === 'development') {\n app.set('json spaces', 2);\n }\n if (trustProxy !== undefined) {\n app.set('trust proxy', trustProxy);\n }\n app.use(middleware.helmet());\n app.use(middleware.cors());\n app.use(middleware.compression());\n app.use(middleware.logging());\n app.use(healthRouter);\n app.use(routes);\n app.use(middleware.notFound());\n app.use(middleware.error());\n },\n });\n\n if (config.has('backend.lifecycle.serverShutdownDelay')) {\n const serverShutdownDelay = readDurationFromConfig(config, {\n key: 'backend.lifecycle.serverShutdownDelay',\n });\n lifecycle.addBeforeShutdownHook(async () => {\n const timeoutMs = durationToMilliseconds(serverShutdownDelay);\n return await new Promise(resolve => {\n setTimeout(resolve, timeoutMs);\n });\n });\n }\n\n lifecycle.addShutdownHook(() => server.stop());\n\n await server.start();\n\n return router;\n },\n });\n\n/** @public */\nexport const rootHttpRouterServiceFactory = Object.assign(\n rootHttpRouterServiceFactoryWithOptions,\n rootHttpRouterServiceFactoryWithOptions(),\n);\n"],"names":["createServiceFactory","coreServices","config","express","DefaultRootHttpRouter","MiddlewareFactory","createHealthRouter","createHttpServer","readHttpServerOptions","readDurationFromConfig","durationToMilliseconds"],"mappings":"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"}
@@ -6,22 +6,11 @@ var parseGitUrl = require('git-url-parse');
6
6
  var lodash = require('lodash');
7
7
  var minimatch = require('minimatch');
8
8
  var ReadUrlResponseFactory = require('./ReadUrlResponseFactory.cjs.js');
9
- var pThrottle = require('p-throttle');
10
9
 
11
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
12
11
 
13
12
  var parseGitUrl__default = /*#__PURE__*/_interopDefaultCompat(parseGitUrl);
14
- var pThrottle__default = /*#__PURE__*/_interopDefaultCompat(pThrottle);
15
13
 
16
- const throttle = pThrottle__default.default({
17
- limit: 1,
18
- interval: 1e3
19
- });
20
- const throttledFetch = throttle(
21
- async (url, options) => {
22
- return await fetch(url, options);
23
- }
24
- );
25
14
  class BitbucketServerUrlReader {
26
15
  constructor(integration, deps) {
27
16
  this.integration = integration;
@@ -52,7 +41,7 @@ class BitbucketServerUrlReader {
52
41
  );
53
42
  let response;
54
43
  try {
55
- response = await throttledFetch(bitbucketUrl.toString(), {
44
+ response = await fetch(bitbucketUrl.toString(), {
56
45
  headers: {
57
46
  ...requestOptions.headers,
58
47
  ...etag && { "If-None-Match": etag },
@@ -93,7 +82,7 @@ class BitbucketServerUrlReader {
93
82
  url,
94
83
  this.integration.config
95
84
  );
96
- const archiveResponse = await throttledFetch(
85
+ const archiveResponse = await fetch(
97
86
  downloadUrl,
98
87
  integration.getBitbucketServerRequestOptions(this.integration.config)
99
88
  );
@@ -165,7 +154,7 @@ class BitbucketServerUrlReader {
165
154
  const { name: repoName, owner: project, ref: branch } = parseGitUrl__default.default(url);
166
155
  const branchParameter = branch ? `?filterText=${encodeURIComponent(branch)}` : "/default";
167
156
  const branchListUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches${branchParameter}`;
168
- const branchListResponse = await throttledFetch(
157
+ const branchListResponse = await fetch(
169
158
  branchListUrl,
170
159
  integration.getBitbucketServerRequestOptions(this.integration.config)
171
160
  );