@backstage/backend-defaults 0.6.0-next.1 → 0.6.0-next.2
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 +34 -0
- package/config.d.ts +40 -0
- package/dist/entrypoints/auth/authServiceFactory.cjs.js +3 -2
- package/dist/entrypoints/auth/authServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js +2 -1
- package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -1
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js +5 -3
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js.map +1 -1
- package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js +3 -4
- package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js +11 -5
- package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js +23 -0
- package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/createLifecycleMiddleware.cjs.js +64 -0
- package/dist/entrypoints/rootHttpRouter/createLifecycleMiddleware.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js +1 -7
- package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js +27 -6
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js +29 -0
- package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +14 -3
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +3 -3
- package/dist/rootHttpRouter.d.ts +3 -1
- package/package.json +17 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @backstage/backend-defaults
|
|
2
2
|
|
|
3
|
+
## 0.6.0-next.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- fd5d337: Added a new `backend.health.headers` configuration that can be used to set additional headers to include in health check responses.
|
|
8
|
+
|
|
9
|
+
**BREAKING CONSUMERS**: As part of this change the `createHealthRouter` function exported from `@backstage/backend-defaults/rootHttpRouter` now requires the root config service to be passed through the `config` option.
|
|
10
|
+
|
|
11
|
+
- 3f34ea9: Throttles Bitbucket Server API calls
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- dfc8b41: Updated dependency `@opentelemetry/api` to `^1.9.0`.
|
|
16
|
+
- 57e0b11: The user and plugin token verification in the default `AuthService` implementation will no longer forward verification errors to the caller, and instead log them as warnings.
|
|
17
|
+
- 57e0b11: The default `authServiceFactory` now correctly depends on the plugin scoped `Logger` services rather than the root scoped one.
|
|
18
|
+
- 0e9c9fa: Implements the `DefaultRootLifecycleService.addBeforeShutdownHook` method, and updates `DefaultRootHttpRouterService` and `DefaultRootHealthService` to listen to that event to stop accepting traffic and close service connections.
|
|
19
|
+
- d0cbd82: Remove use of the `stoppable` library on the `DefaultRootHttpRouterService` as Node's native http server [close](https://nodejs.org/api/http.html#serverclosecallback) method already drains requests.
|
|
20
|
+
Also, we pass a new `lifecycleMiddleware` to the `rootHttpRouterServiceFactory` configure function that must be called manually if you don't call `applyDefaults`.
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
- @backstage/backend-plugin-api@1.1.0-next.2
|
|
23
|
+
- @backstage/backend-app-api@1.1.0-next.2
|
|
24
|
+
- @backstage/plugin-permission-node@0.8.6-next.2
|
|
25
|
+
- @backstage/errors@1.2.6-next.0
|
|
26
|
+
- @backstage/plugin-auth-node@0.5.5-next.2
|
|
27
|
+
- @backstage/plugin-events-node@0.4.6-next.2
|
|
28
|
+
- @backstage/cli-node@0.2.11-next.1
|
|
29
|
+
- @backstage/config-loader@1.9.3-next.1
|
|
30
|
+
- @backstage/backend-dev-utils@0.1.5
|
|
31
|
+
- @backstage/cli-common@0.1.15
|
|
32
|
+
- @backstage/config@1.3.1-next.0
|
|
33
|
+
- @backstage/integration@1.16.0-next.1
|
|
34
|
+
- @backstage/integration-aws-node@0.1.14-next.0
|
|
35
|
+
- @backstage/types@1.2.0
|
|
36
|
+
|
|
3
37
|
## 0.6.0-next.1
|
|
4
38
|
|
|
5
39
|
### Patch Changes
|
package/config.d.ts
CHANGED
|
@@ -28,6 +28,29 @@ export interface Config {
|
|
|
28
28
|
*/
|
|
29
29
|
baseUrl: string;
|
|
30
30
|
|
|
31
|
+
lifecycle?: {
|
|
32
|
+
/**
|
|
33
|
+
* The maximum time that paused requests will wait for the service to start, before returning an error.
|
|
34
|
+
* Defaults to 5 seconds
|
|
35
|
+
* Supported formats:
|
|
36
|
+
* - A string in the format of '1d', '2 seconds' etc. as supported by the `ms`
|
|
37
|
+
* library.
|
|
38
|
+
* - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'.
|
|
39
|
+
* - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`.
|
|
40
|
+
*/
|
|
41
|
+
startupRequestPauseTimeout?: string | HumanDuration;
|
|
42
|
+
/**
|
|
43
|
+
* The minimum time that the HTTP server will delay the shutdown of the backend. During this delay health checks will be set to failing, allowing traffic to drain.
|
|
44
|
+
* Defaults to 0 seconds.
|
|
45
|
+
* Supported formats:
|
|
46
|
+
* - A string in the format of '1d', '2 seconds' etc. as supported by the `ms`
|
|
47
|
+
* library.
|
|
48
|
+
* - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'.
|
|
49
|
+
* - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`.
|
|
50
|
+
*/
|
|
51
|
+
serverShutdownDelay?: string | HumanDuration;
|
|
52
|
+
};
|
|
53
|
+
|
|
31
54
|
/** Address that the backend should listen to. */
|
|
32
55
|
listen?:
|
|
33
56
|
| string
|
|
@@ -552,6 +575,23 @@ export interface Config {
|
|
|
552
575
|
*/
|
|
553
576
|
csp?: { [policyId: string]: string[] | false };
|
|
554
577
|
|
|
578
|
+
/**
|
|
579
|
+
* Options for the health check service and endpoint.
|
|
580
|
+
*/
|
|
581
|
+
health?: {
|
|
582
|
+
/**
|
|
583
|
+
* Additional headers to always include in the health check response.
|
|
584
|
+
*
|
|
585
|
+
* It can be a good idea to set a header that uniquely identifies your service
|
|
586
|
+
* in a multi-service environment. This ensures that the health check that is
|
|
587
|
+
* configured for your service is actually hitting your service and not another.
|
|
588
|
+
*
|
|
589
|
+
* For example, if using Envoy you can use the `service_name_matcher` configuration
|
|
590
|
+
* and set the `x-envoy-upstream-healthchecked-cluster` header to a matching value.
|
|
591
|
+
*/
|
|
592
|
+
headers?: { [name: string]: string };
|
|
593
|
+
};
|
|
594
|
+
|
|
555
595
|
/**
|
|
556
596
|
* Configuration related to URL reading, used for example for reading catalog info
|
|
557
597
|
* files, scaffolder templates, and techdocs content.
|
|
@@ -21,7 +21,7 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
21
21
|
service: backendPluginApi.coreServices.auth,
|
|
22
22
|
deps: {
|
|
23
23
|
config: backendPluginApi.coreServices.rootConfig,
|
|
24
|
-
logger: backendPluginApi.coreServices.
|
|
24
|
+
logger: backendPluginApi.coreServices.logger,
|
|
25
25
|
discovery: backendPluginApi.coreServices.discovery,
|
|
26
26
|
plugin: backendPluginApi.coreServices.pluginMetadata,
|
|
27
27
|
database: backendPluginApi.coreServices.database,
|
|
@@ -46,7 +46,8 @@ const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
46
46
|
keyDuration
|
|
47
47
|
});
|
|
48
48
|
const userTokens = UserTokenHandler.UserTokenHandler.create({
|
|
49
|
-
discovery
|
|
49
|
+
discovery,
|
|
50
|
+
logger
|
|
50
51
|
});
|
|
51
52
|
const pluginTokens = pluginTokenHandlerDecorator(
|
|
52
53
|
PluginTokenHandler.DefaultPluginTokenHandler.create({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n DefaultPluginTokenHandler,\n PluginTokenHandler,\n} from './plugin/PluginTokenHandler';\nimport { createPluginKeySource } from './plugin/keys/createPluginKeySource';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/**\n * @public\n * This service is used to decorate the default plugin token handler with custom logic.\n */\nexport const pluginTokenHandlerDecoratorServiceRef = createServiceRef<\n (defaultImplementation: PluginTokenHandler) => PluginTokenHandler\n>({\n id: 'core.auth.pluginTokenHandlerDecorator',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {},\n factory: async () => {\n return impl => impl;\n },\n }),\n});\n\n/**\n * Handles token authentication and credentials management.\n *\n * See {@link @backstage/code-plugin-api#AuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const authServiceFactory = createServiceFactory({\n service: coreServices.auth,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.
|
|
1
|
+
{"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport {\n DefaultPluginTokenHandler,\n PluginTokenHandler,\n} from './plugin/PluginTokenHandler';\nimport { createPluginKeySource } from './plugin/keys/createPluginKeySource';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/**\n * @public\n * This service is used to decorate the default plugin token handler with custom logic.\n */\nexport const pluginTokenHandlerDecoratorServiceRef = createServiceRef<\n (defaultImplementation: PluginTokenHandler) => PluginTokenHandler\n>({\n id: 'core.auth.pluginTokenHandlerDecorator',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {},\n factory: async () => {\n return impl => impl;\n },\n }),\n});\n\n/**\n * Handles token authentication and credentials management.\n *\n * See {@link @backstage/code-plugin-api#AuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const authServiceFactory = createServiceFactory({\n service: coreServices.auth,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n discovery: coreServices.discovery,\n plugin: coreServices.pluginMetadata,\n database: coreServices.database,\n pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef,\n },\n async factory({\n config,\n discovery,\n plugin,\n logger,\n database,\n pluginTokenHandlerDecorator,\n }) {\n const disableDefaultAuthPolicy =\n config.getOptionalBoolean(\n 'backend.auth.dangerouslyDisableDefaultAuthPolicy',\n ) ?? false;\n\n const keyDuration = { hours: 1 };\n\n const keySource = await createPluginKeySource({\n config,\n database,\n logger,\n keyDuration,\n });\n\n const userTokens = UserTokenHandler.create({\n discovery,\n logger,\n });\n\n const pluginTokens = pluginTokenHandlerDecorator(\n DefaultPluginTokenHandler.create({\n ownPluginId: plugin.getId(),\n logger,\n keySource,\n keyDuration,\n discovery,\n }),\n );\n\n const externalTokens = ExternalTokenHandler.create({\n ownPluginId: plugin.getId(),\n config,\n logger,\n });\n\n return new DefaultAuthService(\n userTokens,\n pluginTokens,\n externalTokens,\n plugin.getId(),\n disableDefaultAuthPolicy,\n keySource,\n );\n },\n});\n"],"names":["createServiceRef","createServiceFactory","coreServices","createPluginKeySource","UserTokenHandler","DefaultPluginTokenHandler","ExternalTokenHandler","DefaultAuthService"],"mappings":";;;;;;;;;AAkCO,MAAM,wCAAwCA,iCAEnD,CAAA;AAAA,EACA,EAAI,EAAA,uCAAA;AAAA,EACJ,cAAA,EAAgB,OAAM,OAAA,KACpBC,qCAAqB,CAAA;AAAA,IACnB,OAAA;AAAA,IACA,MAAM,EAAC;AAAA,IACP,SAAS,YAAY;AACnB,MAAA,OAAO,CAAQ,IAAA,KAAA,IAAA;AAAA;AACjB,GACD;AACL,CAAC;AAWM,MAAM,qBAAqBA,qCAAqB,CAAA;AAAA,EACrD,SAASC,6BAAa,CAAA,IAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,IACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,QAAQA,6BAAa,CAAA,cAAA;AAAA,IACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,IACvB,2BAA6B,EAAA;AAAA,GAC/B;AAAA,EACA,MAAM,OAAQ,CAAA;AAAA,IACZ,MAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GACC,EAAA;AACD,IAAA,MAAM,2BACJ,MAAO,CAAA,kBAAA;AAAA,MACL;AAAA,KACG,IAAA,KAAA;AAEP,IAAM,MAAA,WAAA,GAAc,EAAE,KAAA,EAAO,CAAE,EAAA;AAE/B,IAAM,MAAA,SAAA,GAAY,MAAMC,2CAAsB,CAAA;AAAA,MAC5C,MAAA;AAAA,MACA,QAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAM,MAAA,UAAA,GAAaC,kCAAiB,MAAO,CAAA;AAAA,MACzC,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,YAAe,GAAA,2BAAA;AAAA,MACnBC,6CAA0B,MAAO,CAAA;AAAA,QAC/B,WAAA,EAAa,OAAO,KAAM,EAAA;AAAA,QAC1B,MAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA;AAAA,QACA;AAAA,OACD;AAAA,KACH;AAEA,IAAM,MAAA,cAAA,GAAiBC,0CAAqB,MAAO,CAAA;AAAA,MACjD,WAAA,EAAa,OAAO,KAAM,EAAA;AAAA,MAC1B,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,OAAO,IAAIC,qCAAA;AAAA,MACT,UAAA;AAAA,MACA,YAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAO,KAAM,EAAA;AAAA,MACb,wBAAA;AAAA,MACA;AAAA,KACF;AAAA;AAEJ,CAAC;;;;;"}
|
|
@@ -60,7 +60,8 @@ class DefaultPluginTokenHandler {
|
|
|
60
60
|
requiredClaims: ["iat", "exp", "sub", "aud"]
|
|
61
61
|
}
|
|
62
62
|
).catch((e) => {
|
|
63
|
-
|
|
63
|
+
this.logger.warn("Failed to verify incoming plugin token", e);
|
|
64
|
+
throw new errors.AuthenticationError("Failed plugin token verification");
|
|
64
65
|
});
|
|
65
66
|
return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };
|
|
66
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/plugin/PluginTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { decodeJwt, importJWK, SignJWT, decodeProtectedHeader } from 'jose';\nimport { assertError, AuthenticationError } from '@backstage/errors';\nimport { jwtVerify } from 'jose';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport { JwksClient } from '../JwksClient';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { PluginKeySource } from './keys/types';\n\nconst SECONDS_IN_MS = 1000;\n\nconst ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;\n\ntype Options = {\n ownPluginId: string;\n keyDuration: HumanDuration;\n keySource: PluginKeySource;\n discovery: DiscoveryService;\n logger: LoggerService;\n /**\n * JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose\n */\n algorithm?: string;\n};\n\n/**\n * @public\n * Issues and verifies {@link https://backstage.iceio/docs/auth/service-to-service-auth | service-to-service tokens}.\n */\nexport interface PluginTokenHandler {\n verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined>;\n issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }>;\n}\n\nexport class DefaultPluginTokenHandler implements PluginTokenHandler {\n private jwksMap = new Map<string, JwksClient>();\n\n // Tracking state for isTargetPluginSupported\n private supportedTargetPlugins = new Set<string>();\n private targetPluginInflightChecks = new Map<string, Promise<boolean>>();\n\n static create(options: Options) {\n return new DefaultPluginTokenHandler(\n options.logger,\n options.ownPluginId,\n options.keySource,\n options.algorithm ?? 'ES256',\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.discovery,\n );\n }\n\n private constructor(\n private readonly logger: LoggerService,\n private readonly ownPluginId: string,\n private readonly keySource: PluginKeySource,\n private readonly algorithm: string,\n private readonly keyDurationSeconds: number,\n private readonly discovery: DiscoveryService,\n ) {}\n\n async verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined> {\n try {\n const { typ } = decodeProtectedHeader(token);\n if (typ !== tokenTypes.plugin.typParam) {\n return undefined;\n }\n } catch {\n return undefined;\n }\n\n const pluginId = String(decodeJwt(token).sub);\n if (!pluginId) {\n throw new AuthenticationError('Invalid plugin token: missing subject');\n }\n if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {\n throw new AuthenticationError(\n 'Invalid plugin token: forbidden subject format',\n );\n }\n\n const jwksClient = await this.getJwksClient(pluginId);\n await jwksClient.refreshKeyStore(token); // TODO(Rugvip): Refactor so that this isn't needed\n\n const { payload } = await jwtVerify<{ sub: string; obo?: string }>(\n token,\n jwksClient.getKey,\n {\n typ: tokenTypes.plugin.typParam,\n audience: this.ownPluginId,\n requiredClaims: ['iat', 'exp', 'sub', 'aud'],\n },\n ).catch(e => {\n throw new AuthenticationError('Invalid plugin token', e);\n });\n\n return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };\n }\n\n async issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }> {\n const { pluginId, targetPluginId, onBehalfOf } = options;\n const key = await this.keySource.getPrivateSigningKey();\n\n const sub = pluginId;\n const aud = targetPluginId;\n const iat = Math.floor(Date.now() / SECONDS_IN_MS);\n const ourExp = iat + this.keyDurationSeconds;\n const exp = onBehalfOf\n ? Math.min(\n ourExp,\n Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS),\n )\n : ourExp;\n\n const claims = { sub, aud, iat, exp, obo: onBehalfOf?.limitedUserToken };\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.plugin.typParam,\n alg: this.algorithm,\n kid: key.kid,\n })\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n\n return { token };\n }\n\n private async isTargetPluginSupported(\n targetPluginId: string,\n ): Promise<boolean> {\n if (this.supportedTargetPlugins.has(targetPluginId)) {\n return true;\n }\n const inFlight = this.targetPluginInflightChecks.get(targetPluginId);\n if (inFlight) {\n return inFlight;\n }\n\n const doCheck = async () => {\n try {\n const res = await fetch(\n `${await this.discovery.getBaseUrl(\n targetPluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n if (res.status === 404) {\n return false;\n }\n\n if (!res.ok) {\n throw new Error(`Failed to fetch jwks.json, ${res.status}`);\n }\n\n const data = await res.json();\n if (!data.keys) {\n throw new Error(`Invalid jwks.json response, missing keys`);\n }\n\n this.supportedTargetPlugins.add(targetPluginId);\n return true;\n } catch (error) {\n assertError(error);\n this.logger.error('Unexpected failure for target JWKS check', error);\n return false;\n } finally {\n this.targetPluginInflightChecks.delete(targetPluginId);\n }\n };\n\n const check = doCheck();\n this.targetPluginInflightChecks.set(targetPluginId, check);\n return check;\n }\n\n private async getJwksClient(pluginId: string) {\n const client = this.jwksMap.get(pluginId);\n if (client) {\n return client;\n }\n\n // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (!(await this.isTargetPluginSupported(pluginId))) {\n throw new AuthenticationError(\n `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` +\n 'The target plugin needs to be migrated to be installed in an app using the new backend system.',\n );\n }\n\n const newClient = new JwksClient(async () => {\n return new URL(\n `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n });\n\n this.jwksMap.set(pluginId, newClient);\n return newClient;\n }\n}\n"],"names":["durationToMilliseconds","decodeProtectedHeader","tokenTypes","decodeJwt","AuthenticationError","jwtVerify","SignJWT","importJWK","assertError","JwksClient"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/plugin/PluginTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { decodeJwt, importJWK, SignJWT, decodeProtectedHeader } from 'jose';\nimport { assertError, AuthenticationError } from '@backstage/errors';\nimport { jwtVerify } from 'jose';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport { JwksClient } from '../JwksClient';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { PluginKeySource } from './keys/types';\n\nconst SECONDS_IN_MS = 1000;\n\nconst ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;\n\ntype Options = {\n ownPluginId: string;\n keyDuration: HumanDuration;\n keySource: PluginKeySource;\n discovery: DiscoveryService;\n logger: LoggerService;\n /**\n * JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose\n */\n algorithm?: string;\n};\n\n/**\n * @public\n * Issues and verifies {@link https://backstage.iceio/docs/auth/service-to-service-auth | service-to-service tokens}.\n */\nexport interface PluginTokenHandler {\n verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined>;\n issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }>;\n}\n\nexport class DefaultPluginTokenHandler implements PluginTokenHandler {\n private jwksMap = new Map<string, JwksClient>();\n\n // Tracking state for isTargetPluginSupported\n private supportedTargetPlugins = new Set<string>();\n private targetPluginInflightChecks = new Map<string, Promise<boolean>>();\n\n static create(options: Options) {\n return new DefaultPluginTokenHandler(\n options.logger,\n options.ownPluginId,\n options.keySource,\n options.algorithm ?? 'ES256',\n Math.round(durationToMilliseconds(options.keyDuration) / 1000),\n options.discovery,\n );\n }\n\n private constructor(\n private readonly logger: LoggerService,\n private readonly ownPluginId: string,\n private readonly keySource: PluginKeySource,\n private readonly algorithm: string,\n private readonly keyDurationSeconds: number,\n private readonly discovery: DiscoveryService,\n ) {}\n\n async verifyToken(\n token: string,\n ): Promise<{ subject: string; limitedUserToken?: string } | undefined> {\n try {\n const { typ } = decodeProtectedHeader(token);\n if (typ !== tokenTypes.plugin.typParam) {\n return undefined;\n }\n } catch {\n return undefined;\n }\n\n const pluginId = String(decodeJwt(token).sub);\n if (!pluginId) {\n throw new AuthenticationError('Invalid plugin token: missing subject');\n }\n if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {\n throw new AuthenticationError(\n 'Invalid plugin token: forbidden subject format',\n );\n }\n\n const jwksClient = await this.getJwksClient(pluginId);\n await jwksClient.refreshKeyStore(token); // TODO(Rugvip): Refactor so that this isn't needed\n\n const { payload } = await jwtVerify<{ sub: string; obo?: string }>(\n token,\n jwksClient.getKey,\n {\n typ: tokenTypes.plugin.typParam,\n audience: this.ownPluginId,\n requiredClaims: ['iat', 'exp', 'sub', 'aud'],\n },\n ).catch(e => {\n this.logger.warn('Failed to verify incoming plugin token', e);\n throw new AuthenticationError('Failed plugin token verification');\n });\n\n return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };\n }\n\n async issueToken(options: {\n pluginId: string;\n targetPluginId: string;\n onBehalfOf?: { limitedUserToken: string; expiresAt: Date };\n }): Promise<{ token: string }> {\n const { pluginId, targetPluginId, onBehalfOf } = options;\n const key = await this.keySource.getPrivateSigningKey();\n\n const sub = pluginId;\n const aud = targetPluginId;\n const iat = Math.floor(Date.now() / SECONDS_IN_MS);\n const ourExp = iat + this.keyDurationSeconds;\n const exp = onBehalfOf\n ? Math.min(\n ourExp,\n Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS),\n )\n : ourExp;\n\n const claims = { sub, aud, iat, exp, obo: onBehalfOf?.limitedUserToken };\n const token = await new SignJWT(claims)\n .setProtectedHeader({\n typ: tokenTypes.plugin.typParam,\n alg: this.algorithm,\n kid: key.kid,\n })\n .setAudience(aud)\n .setSubject(sub)\n .setIssuedAt(iat)\n .setExpirationTime(exp)\n .sign(await importJWK(key));\n\n return { token };\n }\n\n private async isTargetPluginSupported(\n targetPluginId: string,\n ): Promise<boolean> {\n if (this.supportedTargetPlugins.has(targetPluginId)) {\n return true;\n }\n const inFlight = this.targetPluginInflightChecks.get(targetPluginId);\n if (inFlight) {\n return inFlight;\n }\n\n const doCheck = async () => {\n try {\n const res = await fetch(\n `${await this.discovery.getBaseUrl(\n targetPluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n if (res.status === 404) {\n return false;\n }\n\n if (!res.ok) {\n throw new Error(`Failed to fetch jwks.json, ${res.status}`);\n }\n\n const data = await res.json();\n if (!data.keys) {\n throw new Error(`Invalid jwks.json response, missing keys`);\n }\n\n this.supportedTargetPlugins.add(targetPluginId);\n return true;\n } catch (error) {\n assertError(error);\n this.logger.error('Unexpected failure for target JWKS check', error);\n return false;\n } finally {\n this.targetPluginInflightChecks.delete(targetPluginId);\n }\n };\n\n const check = doCheck();\n this.targetPluginInflightChecks.set(targetPluginId, check);\n return check;\n }\n\n private async getJwksClient(pluginId: string) {\n const client = this.jwksMap.get(pluginId);\n if (client) {\n return client;\n }\n\n // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set\n if (!(await this.isTargetPluginSupported(pluginId))) {\n throw new AuthenticationError(\n `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` +\n 'The target plugin needs to be migrated to be installed in an app using the new backend system.',\n );\n }\n\n const newClient = new JwksClient(async () => {\n return new URL(\n `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.backstage/auth/v1/jwks.json`,\n );\n });\n\n this.jwksMap.set(pluginId, newClient);\n return newClient;\n }\n}\n"],"names":["durationToMilliseconds","decodeProtectedHeader","tokenTypes","decodeJwt","AuthenticationError","jwtVerify","SignJWT","importJWK","assertError","JwksClient"],"mappings":"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"}
|
|
@@ -6,15 +6,16 @@ var jose = require('jose');
|
|
|
6
6
|
var JwksClient = require('../JwksClient.cjs.js');
|
|
7
7
|
|
|
8
8
|
class UserTokenHandler {
|
|
9
|
-
constructor(jwksClient) {
|
|
9
|
+
constructor(jwksClient, logger) {
|
|
10
10
|
this.jwksClient = jwksClient;
|
|
11
|
+
this.logger = logger;
|
|
11
12
|
}
|
|
12
13
|
static create(options) {
|
|
13
14
|
const jwksClient = new JwksClient.JwksClient(async () => {
|
|
14
15
|
const url = await options.discovery.getBaseUrl("auth");
|
|
15
16
|
return new URL(`${url}/.well-known/jwks.json`);
|
|
16
17
|
});
|
|
17
|
-
return new UserTokenHandler(jwksClient);
|
|
18
|
+
return new UserTokenHandler(jwksClient, options.logger);
|
|
18
19
|
}
|
|
19
20
|
async verifyToken(token) {
|
|
20
21
|
const verifyOpts = this.#getTokenVerificationOptions(token);
|
|
@@ -27,7 +28,8 @@ class UserTokenHandler {
|
|
|
27
28
|
this.jwksClient.getKey,
|
|
28
29
|
verifyOpts
|
|
29
30
|
).catch((e) => {
|
|
30
|
-
|
|
31
|
+
this.logger.warn("Failed to verify incoming user token", e);
|
|
32
|
+
throw new errors.AuthenticationError("Failed user token verification");
|
|
31
33
|
});
|
|
32
34
|
const userEntityRef = payload.sub;
|
|
33
35
|
if (!userEntityRef) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/user/UserTokenHandler.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 { DiscoveryService } from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport {\n base64url,\n decodeJwt,\n decodeProtectedHeader,\n jwtVerify,\n JWTVerifyOptions,\n} from 'jose';\nimport { JwksClient } from '../JwksClient';\n\n/**\n * An identity client to interact with auth-backend and authenticate Backstage\n * tokens\n *\n * @internal\n */\nexport class UserTokenHandler {\n static create(options: {
|
|
1
|
+
{"version":3,"file":"UserTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/user/UserTokenHandler.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 { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { tokenTypes } from '@backstage/plugin-auth-node';\nimport {\n base64url,\n decodeJwt,\n decodeProtectedHeader,\n jwtVerify,\n JWTVerifyOptions,\n} from 'jose';\nimport { JwksClient } from '../JwksClient';\n\n/**\n * An identity client to interact with auth-backend and authenticate Backstage\n * tokens\n *\n * @internal\n */\nexport class UserTokenHandler {\n static create(options: {\n discovery: DiscoveryService;\n logger: LoggerService;\n }): UserTokenHandler {\n const jwksClient = new JwksClient(async () => {\n const url = await options.discovery.getBaseUrl('auth');\n return new URL(`${url}/.well-known/jwks.json`);\n });\n return new UserTokenHandler(jwksClient, options.logger);\n }\n\n constructor(\n private readonly jwksClient: JwksClient,\n private readonly logger: LoggerService,\n ) {}\n\n async verifyToken(token: string) {\n const verifyOpts = this.#getTokenVerificationOptions(token);\n if (!verifyOpts) {\n return undefined;\n }\n\n await this.jwksClient.refreshKeyStore(token);\n\n // Verify a limited token, ensuring the necessarily claims are present and token type is correct\n const { payload } = await jwtVerify(\n token,\n this.jwksClient.getKey,\n verifyOpts,\n ).catch(e => {\n this.logger.warn('Failed to verify incoming user token', e);\n throw new AuthenticationError('Failed user token verification');\n });\n\n const userEntityRef = payload.sub;\n\n if (!userEntityRef) {\n throw new AuthenticationError('No user sub found in token');\n }\n\n return { userEntityRef };\n }\n\n #getTokenVerificationOptions(token: string): JWTVerifyOptions | undefined {\n try {\n const { typ } = decodeProtectedHeader(token);\n\n if (typ === tokenTypes.user.typParam) {\n return {\n requiredClaims: ['iat', 'exp', 'sub'],\n typ: tokenTypes.user.typParam,\n };\n }\n\n if (typ === tokenTypes.limitedUser.typParam) {\n return {\n requiredClaims: ['iat', 'exp', 'sub'],\n typ: tokenTypes.limitedUser.typParam,\n };\n }\n\n const { aud } = decodeJwt(token);\n if (aud === tokenTypes.user.audClaim) {\n return {\n audience: tokenTypes.user.audClaim,\n };\n }\n } catch {\n /* ignore */\n }\n\n return undefined;\n }\n\n createLimitedUserToken(backstageToken: string) {\n const [headerRaw, payloadRaw] = backstageToken.split('.');\n const header = JSON.parse(\n new TextDecoder().decode(base64url.decode(headerRaw)),\n );\n const payload = JSON.parse(\n new TextDecoder().decode(base64url.decode(payloadRaw)),\n );\n\n const tokenType = header.typ;\n\n // Only new user tokens can be used to create a limited user token. If we\n // can't create a limited token, or the token is already a limited one, we\n // return the original token\n if (!tokenType || tokenType === tokenTypes.limitedUser.typParam) {\n return { token: backstageToken, expiresAt: new Date(payload.exp * 1000) };\n }\n\n if (tokenType !== tokenTypes.user.typParam) {\n throw new AuthenticationError(\n 'Failed to create limited user token, invalid token type',\n );\n }\n\n // NOTE: The order and properties in both the header and payload must match\n // the usage in plugins/auth-backend/src/identity/TokenFactory.ts\n const limitedUserToken = [\n base64url.encode(\n JSON.stringify({\n typ: tokenTypes.limitedUser.typParam,\n alg: header.alg,\n kid: header.kid,\n }),\n ),\n base64url.encode(\n JSON.stringify({\n sub: payload.sub,\n iat: payload.iat,\n exp: payload.exp,\n }),\n ),\n payload.uip,\n ].join('.');\n\n return { token: limitedUserToken, expiresAt: new Date(payload.exp * 1000) };\n }\n\n isLimitedUserToken(token: string): boolean {\n try {\n const { typ } = decodeProtectedHeader(token);\n return typ === tokenTypes.limitedUser.typParam;\n } catch {\n return false;\n }\n }\n}\n"],"names":["JwksClient","jwtVerify","AuthenticationError","decodeProtectedHeader","tokenTypes","decodeJwt","base64url"],"mappings":"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"}
|
|
@@ -4,7 +4,8 @@ var Router = require('express-promise-router');
|
|
|
4
4
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
5
5
|
var createAuthIntegrationRouter = require('./http/createAuthIntegrationRouter.cjs.js');
|
|
6
6
|
var createCredentialsBarrier = require('./http/createCredentialsBarrier.cjs.js');
|
|
7
|
-
|
|
7
|
+
require('@backstage/errors');
|
|
8
|
+
require('@backstage/types');
|
|
8
9
|
var createCookieAuthRefreshMiddleware = require('./http/createCookieAuthRefreshMiddleware.cjs.js');
|
|
9
10
|
|
|
10
11
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
@@ -17,12 +18,11 @@ const httpRouterServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
17
18
|
deps: {
|
|
18
19
|
plugin: backendPluginApi.coreServices.pluginMetadata,
|
|
19
20
|
config: backendPluginApi.coreServices.rootConfig,
|
|
20
|
-
lifecycle: backendPluginApi.coreServices.lifecycle,
|
|
21
21
|
rootHttpRouter: backendPluginApi.coreServices.rootHttpRouter,
|
|
22
22
|
auth: backendPluginApi.coreServices.auth,
|
|
23
23
|
httpAuth: backendPluginApi.coreServices.httpAuth
|
|
24
24
|
},
|
|
25
|
-
async factory({ auth, httpAuth, config, plugin, rootHttpRouter
|
|
25
|
+
async factory({ auth, httpAuth, config, plugin, rootHttpRouter }) {
|
|
26
26
|
const router = Router__default.default();
|
|
27
27
|
rootHttpRouter.use(`/api/${plugin.getId()}`, router);
|
|
28
28
|
const credentialsBarrier = createCredentialsBarrier.createCredentialsBarrier({
|
|
@@ -30,7 +30,6 @@ const httpRouterServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
30
30
|
config
|
|
31
31
|
});
|
|
32
32
|
router.use(createAuthIntegrationRouter.createAuthIntegrationRouter({ auth }));
|
|
33
|
-
router.use(createLifecycleMiddleware.createLifecycleMiddleware({ lifecycle }));
|
|
34
33
|
router.use(credentialsBarrier.middleware);
|
|
35
34
|
router.use(createCookieAuthRefreshMiddleware.createCookieAuthRefreshMiddleware({ auth, httpAuth }));
|
|
36
35
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpRouterServiceFactory.cjs.js","sources":["../../../src/entrypoints/httpRouter/httpRouterServiceFactory.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 { Handler } from 'express';\nimport PromiseRouter from 'express-promise-router';\nimport {\n coreServices,\n createServiceFactory,\n HttpRouterServiceAuthPolicy,\n} from '@backstage/backend-plugin-api';\nimport {\n
|
|
1
|
+
{"version":3,"file":"httpRouterServiceFactory.cjs.js","sources":["../../../src/entrypoints/httpRouter/httpRouterServiceFactory.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 { Handler } from 'express';\nimport PromiseRouter from 'express-promise-router';\nimport {\n coreServices,\n createServiceFactory,\n HttpRouterServiceAuthPolicy,\n} from '@backstage/backend-plugin-api';\nimport {\n createCookieAuthRefreshMiddleware,\n createCredentialsBarrier,\n createAuthIntegrationRouter,\n} from './http';\n\n/**\n * HTTP route registration for plugins.\n *\n * See {@link @backstage/code-plugin-api#HttpRouterService}\n * and {@link https://backstage.io/docs/backend-system/core-services/http-router | the service docs}\n * for more information.\n *\n * @public\n */\nexport const httpRouterServiceFactory = createServiceFactory({\n service: coreServices.httpRouter,\n initialization: 'always',\n deps: {\n plugin: coreServices.pluginMetadata,\n config: coreServices.rootConfig,\n rootHttpRouter: coreServices.rootHttpRouter,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n },\n async factory({ auth, httpAuth, config, plugin, rootHttpRouter }) {\n const router = PromiseRouter();\n\n rootHttpRouter.use(`/api/${plugin.getId()}`, router);\n\n const credentialsBarrier = createCredentialsBarrier({\n httpAuth,\n config,\n });\n\n router.use(createAuthIntegrationRouter({ auth }));\n router.use(credentialsBarrier.middleware);\n router.use(createCookieAuthRefreshMiddleware({ auth, httpAuth }));\n\n return {\n use(handler: Handler): void {\n router.use(handler);\n },\n addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void {\n credentialsBarrier.addAuthPolicy(policy);\n },\n };\n },\n});\n"],"names":["createServiceFactory","coreServices","PromiseRouter","createCredentialsBarrier","createAuthIntegrationRouter","createCookieAuthRefreshMiddleware"],"mappings":";;;;;;;;;;;;;;AAsCO,MAAM,2BAA2BA,qCAAqB,CAAA;AAAA,EAC3D,SAASC,6BAAa,CAAA,UAAA;AAAA,EACtB,cAAgB,EAAA,QAAA;AAAA,EAChB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,cAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,gBAAgBA,6BAAa,CAAA,cAAA;AAAA,IAC7B,MAAMA,6BAAa,CAAA,IAAA;AAAA,IACnB,UAAUA,6BAAa,CAAA;AAAA,GACzB;AAAA,EACA,MAAM,QAAQ,EAAE,IAAA,EAAM,UAAU,MAAQ,EAAA,MAAA,EAAQ,gBAAkB,EAAA;AAChE,IAAA,MAAM,SAASC,uBAAc,EAAA;AAE7B,IAAA,cAAA,CAAe,IAAI,CAAQ,KAAA,EAAA,MAAA,CAAO,KAAM,EAAC,IAAI,MAAM,CAAA;AAEnD,IAAA,MAAM,qBAAqBC,iDAAyB,CAAA;AAAA,MAClD,QAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAA,CAAO,GAAI,CAAAC,uDAAA,CAA4B,EAAE,IAAA,EAAM,CAAC,CAAA;AAChD,IAAO,MAAA,CAAA,GAAA,CAAI,mBAAmB,UAAU,CAAA;AACxC,IAAA,MAAA,CAAO,IAAIC,mEAAkC,CAAA,EAAE,IAAM,EAAA,QAAA,EAAU,CAAC,CAAA;AAEhE,IAAO,OAAA;AAAA,MACL,IAAI,OAAwB,EAAA;AAC1B,QAAA,MAAA,CAAO,IAAI,OAAO,CAAA;AAAA,OACpB;AAAA,MACA,cAAc,MAA2C,EAAA;AACvD,QAAA,kBAAA,CAAmB,cAAc,MAAM,CAAA;AAAA;AACzC,KACF;AAAA;AAEJ,CAAC;;;;"}
|
|
@@ -6,23 +6,29 @@ class DefaultRootHealthService {
|
|
|
6
6
|
constructor(options) {
|
|
7
7
|
this.options = options;
|
|
8
8
|
options.lifecycle.addStartupHook(() => {
|
|
9
|
-
this.#
|
|
9
|
+
this.#state = "up";
|
|
10
10
|
});
|
|
11
|
-
options.lifecycle.
|
|
12
|
-
this.#
|
|
11
|
+
options.lifecycle.addBeforeShutdownHook(() => {
|
|
12
|
+
this.#state = "down";
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
-
#
|
|
15
|
+
#state = "init";
|
|
16
16
|
async getLiveness() {
|
|
17
17
|
return { status: 200, payload: { status: "ok" } };
|
|
18
18
|
}
|
|
19
19
|
async getReadiness() {
|
|
20
|
-
if (
|
|
20
|
+
if (this.#state === "init") {
|
|
21
21
|
return {
|
|
22
22
|
status: 503,
|
|
23
23
|
payload: { message: "Backend has not started yet", status: "error" }
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
+
if (this.#state === "down") {
|
|
27
|
+
return {
|
|
28
|
+
status: 503,
|
|
29
|
+
payload: { message: "Backend is shuttting down", status: "error" }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
26
32
|
return { status: 200, payload: { status: "ok" } };
|
|
27
33
|
}
|
|
28
34
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rootHealthServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootHealth/rootHealthServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n RootHealthService,\n RootLifecycleService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport class DefaultRootHealthService implements RootHealthService {\n #
|
|
1
|
+
{"version":3,"file":"rootHealthServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootHealth/rootHealthServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n RootHealthService,\n RootLifecycleService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport class DefaultRootHealthService implements RootHealthService {\n #state: 'init' | 'up' | 'down' = 'init';\n\n constructor(readonly options: { lifecycle: RootLifecycleService }) {\n options.lifecycle.addStartupHook(() => {\n this.#state = 'up';\n });\n options.lifecycle.addBeforeShutdownHook(() => {\n this.#state = 'down';\n });\n }\n\n async getLiveness(): Promise<{ status: number; payload?: any }> {\n return { status: 200, payload: { status: 'ok' } };\n }\n\n async getReadiness(): Promise<{ status: number; payload?: any }> {\n if (this.#state === 'init') {\n return {\n status: 503,\n payload: { message: 'Backend has not started yet', status: 'error' },\n };\n }\n if (this.#state === 'down') {\n return {\n status: 503,\n payload: { message: 'Backend is shuttting down', status: 'error' },\n };\n }\n\n return { status: 200, payload: { status: 'ok' } };\n }\n}\n\n/**\n * @public\n */\nexport const rootHealthServiceFactory = createServiceFactory({\n service: coreServices.rootHealth,\n deps: {\n lifecycle: coreServices.rootLifecycle,\n },\n async factory({ lifecycle }) {\n return new DefaultRootHealthService({ lifecycle });\n },\n});\n"],"names":["createServiceFactory","coreServices"],"mappings":";;;;AAwBO,MAAM,wBAAsD,CAAA;AAAA,EAGjE,YAAqB,OAA8C,EAAA;AAA9C,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AACnB,IAAQ,OAAA,CAAA,SAAA,CAAU,eAAe,MAAM;AACrC,MAAA,IAAA,CAAK,MAAS,GAAA,IAAA;AAAA,KACf,CAAA;AACD,IAAQ,OAAA,CAAA,SAAA,CAAU,sBAAsB,MAAM;AAC5C,MAAA,IAAA,CAAK,MAAS,GAAA,MAAA;AAAA,KACf,CAAA;AAAA;AACH,EATA,MAAiC,GAAA,MAAA;AAAA,EAWjC,MAAM,WAA0D,GAAA;AAC9D,IAAA,OAAO,EAAE,MAAQ,EAAA,GAAA,EAAK,SAAS,EAAE,MAAA,EAAQ,MAAO,EAAA;AAAA;AAClD,EAEA,MAAM,YAA2D,GAAA;AAC/D,IAAI,IAAA,IAAA,CAAK,WAAW,MAAQ,EAAA;AAC1B,MAAO,OAAA;AAAA,QACL,MAAQ,EAAA,GAAA;AAAA,QACR,OAAS,EAAA,EAAE,OAAS,EAAA,6BAAA,EAA+B,QAAQ,OAAQ;AAAA,OACrE;AAAA;AAEF,IAAI,IAAA,IAAA,CAAK,WAAW,MAAQ,EAAA;AAC1B,MAAO,OAAA;AAAA,QACL,MAAQ,EAAA,GAAA;AAAA,QACR,OAAS,EAAA,EAAE,OAAS,EAAA,2BAAA,EAA6B,QAAQ,OAAQ;AAAA,OACnE;AAAA;AAGF,IAAA,OAAO,EAAE,MAAQ,EAAA,GAAA,EAAK,SAAS,EAAE,MAAA,EAAQ,MAAO,EAAA;AAAA;AAEpD;AAKO,MAAM,2BAA2BA,qCAAqB,CAAA;AAAA,EAC3D,SAASC,6BAAa,CAAA,UAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,WAAWA,6BAAa,CAAA;AAAA,GAC1B;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,SAAA,EAAa,EAAA;AAC3B,IAAA,OAAO,IAAI,wBAAA,CAAyB,EAAE,SAAA,EAAW,CAAA;AAAA;AAErD,CAAC;;;;;"}
|
|
@@ -6,12 +6,32 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
|
|
|
6
6
|
|
|
7
7
|
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
8
8
|
|
|
9
|
+
const HEADER_CONFIG_KEY = "backend.health.headers";
|
|
9
10
|
function createHealthRouter(options) {
|
|
11
|
+
const headersConfig = options.config.getOptionalConfig(HEADER_CONFIG_KEY)?.get();
|
|
12
|
+
if (headersConfig) {
|
|
13
|
+
for (const [key, value] of Object.entries(headersConfig)) {
|
|
14
|
+
if (!key || typeof key !== "string") {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Invalid header name in at ${HEADER_CONFIG_KEY}, must be a non-empty string`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
if (!value || typeof value !== "string") {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Invalid header value in at ${HEADER_CONFIG_KEY}, must be a non-empty string`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const headers = headersConfig && new Headers(headersConfig);
|
|
10
27
|
const router = Router__default.default();
|
|
11
28
|
router.get(
|
|
12
29
|
"/.backstage/health/v1/readiness",
|
|
13
30
|
async (_request, response) => {
|
|
14
31
|
const { status, payload } = await options.health.getReadiness();
|
|
32
|
+
if (headers) {
|
|
33
|
+
response.setHeaders(headers);
|
|
34
|
+
}
|
|
15
35
|
response.status(status).json(payload);
|
|
16
36
|
}
|
|
17
37
|
);
|
|
@@ -19,6 +39,9 @@ function createHealthRouter(options) {
|
|
|
19
39
|
"/.backstage/health/v1/liveness",
|
|
20
40
|
async (_request, response) => {
|
|
21
41
|
const { status, payload } = await options.health.getLiveness();
|
|
42
|
+
if (headers) {
|
|
43
|
+
response.setHeaders(headers);
|
|
44
|
+
}
|
|
22
45
|
response.status(status).json(payload);
|
|
23
46
|
}
|
|
24
47
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createHealthRouter.cjs.js","sources":["../../../src/entrypoints/rootHttpRouter/createHealthRouter.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {
|
|
1
|
+
{"version":3,"file":"createHealthRouter.cjs.js","sources":["../../../src/entrypoints/rootHttpRouter/createHealthRouter.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n RootConfigService,\n RootHealthService,\n} from '@backstage/backend-plugin-api';\nimport Router from 'express-promise-router';\nimport { Request, Response } from 'express';\n\nconst HEADER_CONFIG_KEY = 'backend.health.headers';\n\n/**\n * @public\n */\nexport function createHealthRouter(options: {\n health: RootHealthService;\n config: RootConfigService;\n}) {\n const headersConfig = options.config\n .getOptionalConfig(HEADER_CONFIG_KEY)\n ?.get();\n if (headersConfig) {\n for (const [key, value] of Object.entries(headersConfig)) {\n if (!key || typeof key !== 'string') {\n throw new Error(\n `Invalid header name in at ${HEADER_CONFIG_KEY}, must be a non-empty string`,\n );\n }\n if (!value || typeof value !== 'string') {\n throw new Error(\n `Invalid header value in at ${HEADER_CONFIG_KEY}, must be a non-empty string`,\n );\n }\n }\n }\n const headers = headersConfig && new Headers(headersConfig as HeadersInit);\n\n const router = Router();\n\n router.get(\n '/.backstage/health/v1/readiness',\n async (_request: Request, response: Response) => {\n const { status, payload } = await options.health.getReadiness();\n if (headers) {\n response.setHeaders(headers);\n }\n response.status(status).json(payload);\n },\n );\n\n router.get(\n '/.backstage/health/v1/liveness',\n async (_request: Request, response: Response) => {\n const { status, payload } = await options.health.getLiveness();\n if (headers) {\n response.setHeaders(headers);\n }\n response.status(status).json(payload);\n },\n );\n\n return router;\n}\n"],"names":["Router"],"mappings":";;;;;;;;AAuBA,MAAM,iBAAoB,GAAA,wBAAA;AAKnB,SAAS,mBAAmB,OAGhC,EAAA;AACD,EAAA,MAAM,gBAAgB,OAAQ,CAAA,MAAA,CAC3B,iBAAkB,CAAA,iBAAiB,GAClC,GAAI,EAAA;AACR,EAAA,IAAI,aAAe,EAAA;AACjB,IAAA,KAAA,MAAW,CAAC,GAAK,EAAA,KAAK,KAAK,MAAO,CAAA,OAAA,CAAQ,aAAa,CAAG,EAAA;AACxD,MAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAU,EAAA;AACnC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,6BAA6B,iBAAiB,CAAA,4BAAA;AAAA,SAChD;AAAA;AAEF,MAAA,IAAI,CAAC,KAAA,IAAS,OAAO,KAAA,KAAU,QAAU,EAAA;AACvC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,8BAA8B,iBAAiB,CAAA,4BAAA;AAAA,SACjD;AAAA;AACF;AACF;AAEF,EAAA,MAAM,OAAU,GAAA,aAAA,IAAiB,IAAI,OAAA,CAAQ,aAA4B,CAAA;AAEzE,EAAA,MAAM,SAASA,uBAAO,EAAA;AAEtB,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,iCAAA;AAAA,IACA,OAAO,UAAmB,QAAuB,KAAA;AAC/C,MAAA,MAAM,EAAE,MAAQ,EAAA,OAAA,KAAY,MAAM,OAAA,CAAQ,OAAO,YAAa,EAAA;AAC9D,MAAA,IAAI,OAAS,EAAA;AACX,QAAA,QAAA,CAAS,WAAW,OAAO,CAAA;AAAA;AAE7B,MAAA,QAAA,CAAS,MAAO,CAAA,MAAM,CAAE,CAAA,IAAA,CAAK,OAAO,CAAA;AAAA;AACtC,GACF;AAEA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,gCAAA;AAAA,IACA,OAAO,UAAmB,QAAuB,KAAA;AAC/C,MAAA,MAAM,EAAE,MAAQ,EAAA,OAAA,KAAY,MAAM,OAAA,CAAQ,OAAO,WAAY,EAAA;AAC7D,MAAA,IAAI,OAAS,EAAA;AACX,QAAA,QAAA,CAAS,WAAW,OAAO,CAAA;AAAA;AAE7B,MAAA,QAAA,CAAS,MAAO,CAAA,MAAM,CAAE,CAAA,IAAA,CAAK,OAAO,CAAA;AAAA;AACtC,GACF;AAEA,EAAO,OAAA,MAAA;AACT;;;;"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
var types = require('@backstage/types');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_STARTUP_REQUEST_PAUSE_TIMEOUT = { seconds: 5 };
|
|
7
|
+
const DEFAULT_SERVER_SHUTDOWN_TIMEOUT = { seconds: 0 };
|
|
8
|
+
function createLifecycleMiddleware(options) {
|
|
9
|
+
const { lifecycle, startupRequestPauseTimeout, serverShutdownDelay } = options;
|
|
10
|
+
let state = "init";
|
|
11
|
+
const waiting = /* @__PURE__ */ new Set();
|
|
12
|
+
lifecycle.addStartupHook(async () => {
|
|
13
|
+
if (state === "init") {
|
|
14
|
+
state = "up";
|
|
15
|
+
for (const item of waiting) {
|
|
16
|
+
clearTimeout(item.timeout);
|
|
17
|
+
item.next();
|
|
18
|
+
}
|
|
19
|
+
waiting.clear();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
lifecycle.addBeforeShutdownHook(async () => {
|
|
23
|
+
const timeoutMs = types.durationToMilliseconds(
|
|
24
|
+
serverShutdownDelay ?? DEFAULT_SERVER_SHUTDOWN_TIMEOUT
|
|
25
|
+
);
|
|
26
|
+
return await new Promise((resolve) => {
|
|
27
|
+
setTimeout(resolve, timeoutMs);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
lifecycle.addShutdownHook(async () => {
|
|
31
|
+
state = "down";
|
|
32
|
+
for (const item of waiting) {
|
|
33
|
+
clearTimeout(item.timeout);
|
|
34
|
+
item.next(new errors.ServiceUnavailableError("Service is shutting down"));
|
|
35
|
+
}
|
|
36
|
+
waiting.clear();
|
|
37
|
+
});
|
|
38
|
+
return (_req, _res, next) => {
|
|
39
|
+
if (state === "up") {
|
|
40
|
+
next();
|
|
41
|
+
return;
|
|
42
|
+
} else if (state === "down") {
|
|
43
|
+
next(new errors.ServiceUnavailableError("Service is shutting down"));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const timeoutMs = types.durationToMilliseconds(
|
|
47
|
+
startupRequestPauseTimeout ?? DEFAULT_STARTUP_REQUEST_PAUSE_TIMEOUT
|
|
48
|
+
);
|
|
49
|
+
const item = {
|
|
50
|
+
next,
|
|
51
|
+
timeout: setTimeout(() => {
|
|
52
|
+
if (waiting.delete(item)) {
|
|
53
|
+
next(new errors.ServiceUnavailableError("Service has not started up yet"));
|
|
54
|
+
}
|
|
55
|
+
}, timeoutMs)
|
|
56
|
+
};
|
|
57
|
+
waiting.add(item);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exports.DEFAULT_SERVER_SHUTDOWN_TIMEOUT = DEFAULT_SERVER_SHUTDOWN_TIMEOUT;
|
|
62
|
+
exports.DEFAULT_STARTUP_REQUEST_PAUSE_TIMEOUT = DEFAULT_STARTUP_REQUEST_PAUSE_TIMEOUT;
|
|
63
|
+
exports.createLifecycleMiddleware = createLifecycleMiddleware;
|
|
64
|
+
//# sourceMappingURL=createLifecycleMiddleware.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createLifecycleMiddleware.cjs.js","sources":["../../../src/entrypoints/rootHttpRouter/createLifecycleMiddleware.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 { RootLifecycleService } from '@backstage/backend-plugin-api';\nimport { ServiceUnavailableError } from '@backstage/errors';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { RequestHandler } from 'express';\n\nexport const DEFAULT_STARTUP_REQUEST_PAUSE_TIMEOUT = { seconds: 5 };\nexport const DEFAULT_SERVER_SHUTDOWN_TIMEOUT = { seconds: 0 };\n\n/**\n * Options for {@link createLifecycleMiddleware}.\n * @public\n */\nexport interface LifecycleMiddlewareOptions {\n lifecycle: RootLifecycleService;\n /**\n * The maximum time that paused requests will wait for the service to start, before returning an error.\n *\n * Defaults to 5 seconds.\n */\n startupRequestPauseTimeout?: HumanDuration;\n /**\n * The maximum time that the server will wait for stop accepting traffic, before returning an error.\n *\n * Defaults to 0 seconds.\n */\n serverShutdownDelay?: HumanDuration;\n}\n\n/**\n * Creates a middleware that pauses requests until the service has started.\n *\n * @remarks\n *\n * Requests that arrive before the service has started will be paused until startup is complete.\n * If the service does not start within the provided timeout, the request will be rejected with a\n * {@link @backstage/errors#ServiceUnavailableError}.\n *\n * If the service is shutting down, all requests will be rejected with a\n * {@link @backstage/errors#ServiceUnavailableError}.\n *\n * @public\n */\nexport function createLifecycleMiddleware(\n options: LifecycleMiddlewareOptions,\n): RequestHandler {\n const { lifecycle, startupRequestPauseTimeout, serverShutdownDelay } =\n options;\n\n let state: 'init' | 'up' | 'down' = 'init';\n const waiting = new Set<{\n next: (err?: Error) => void;\n timeout: NodeJS.Timeout;\n }>();\n\n lifecycle.addStartupHook(async () => {\n if (state === 'init') {\n state = 'up';\n for (const item of waiting) {\n clearTimeout(item.timeout);\n item.next();\n }\n waiting.clear();\n }\n });\n\n lifecycle.addBeforeShutdownHook(async () => {\n const timeoutMs = durationToMilliseconds(\n serverShutdownDelay ?? DEFAULT_SERVER_SHUTDOWN_TIMEOUT,\n );\n return await new Promise(resolve => {\n setTimeout(resolve, timeoutMs);\n });\n });\n\n lifecycle.addShutdownHook(async () => {\n state = 'down';\n for (const item of waiting) {\n clearTimeout(item.timeout);\n item.next(new ServiceUnavailableError('Service is shutting down'));\n }\n waiting.clear();\n });\n\n return (_req, _res, next) => {\n if (state === 'up') {\n next();\n return;\n } else if (state === 'down') {\n next(new ServiceUnavailableError('Service is shutting down'));\n return;\n }\n\n const timeoutMs = durationToMilliseconds(\n startupRequestPauseTimeout ?? DEFAULT_STARTUP_REQUEST_PAUSE_TIMEOUT,\n );\n\n const item = {\n next,\n timeout: setTimeout(() => {\n if (waiting.delete(item)) {\n next(new ServiceUnavailableError('Service has not started up yet'));\n }\n }, timeoutMs),\n };\n\n waiting.add(item);\n };\n}\n"],"names":["durationToMilliseconds","ServiceUnavailableError"],"mappings":";;;;;AAqBa,MAAA,qCAAA,GAAwC,EAAE,OAAA,EAAS,CAAE;AACrD,MAAA,+BAAA,GAAkC,EAAE,OAAA,EAAS,CAAE;AAoCrD,SAAS,0BACd,OACgB,EAAA;AAChB,EAAA,MAAM,EAAE,SAAA,EAAW,0BAA4B,EAAA,mBAAA,EAC7C,GAAA,OAAA;AAEF,EAAA,IAAI,KAAgC,GAAA,MAAA;AACpC,EAAM,MAAA,OAAA,uBAAc,GAGjB,EAAA;AAEH,EAAA,SAAA,CAAU,eAAe,YAAY;AACnC,IAAA,IAAI,UAAU,MAAQ,EAAA;AACpB,MAAQ,KAAA,GAAA,IAAA;AACR,MAAA,KAAA,MAAW,QAAQ,OAAS,EAAA;AAC1B,QAAA,YAAA,CAAa,KAAK,OAAO,CAAA;AACzB,QAAA,IAAA,CAAK,IAAK,EAAA;AAAA;AAEZ,MAAA,OAAA,CAAQ,KAAM,EAAA;AAAA;AAChB,GACD,CAAA;AAED,EAAA,SAAA,CAAU,sBAAsB,YAAY;AAC1C,IAAA,MAAM,SAAY,GAAAA,4BAAA;AAAA,MAChB,mBAAuB,IAAA;AAAA,KACzB;AACA,IAAO,OAAA,MAAM,IAAI,OAAA,CAAQ,CAAW,OAAA,KAAA;AAClC,MAAA,UAAA,CAAW,SAAS,SAAS,CAAA;AAAA,KAC9B,CAAA;AAAA,GACF,CAAA;AAED,EAAA,SAAA,CAAU,gBAAgB,YAAY;AACpC,IAAQ,KAAA,GAAA,MAAA;AACR,IAAA,KAAA,MAAW,QAAQ,OAAS,EAAA;AAC1B,MAAA,YAAA,CAAa,KAAK,OAAO,CAAA;AACzB,MAAA,IAAA,CAAK,IAAK,CAAA,IAAIC,8BAAwB,CAAA,0BAA0B,CAAC,CAAA;AAAA;AAEnE,IAAA,OAAA,CAAQ,KAAM,EAAA;AAAA,GACf,CAAA;AAED,EAAO,OAAA,CAAC,IAAM,EAAA,IAAA,EAAM,IAAS,KAAA;AAC3B,IAAA,IAAI,UAAU,IAAM,EAAA;AAClB,MAAK,IAAA,EAAA;AACL,MAAA;AAAA,KACF,MAAA,IAAW,UAAU,MAAQ,EAAA;AAC3B,MAAK,IAAA,CAAA,IAAIA,8BAAwB,CAAA,0BAA0B,CAAC,CAAA;AAC5D,MAAA;AAAA;AAGF,IAAA,MAAM,SAAY,GAAAD,4BAAA;AAAA,MAChB,0BAA8B,IAAA;AAAA,KAChC;AAEA,IAAA,MAAM,IAAO,GAAA;AAAA,MACX,IAAA;AAAA,MACA,OAAA,EAAS,WAAW,MAAM;AACxB,QAAI,IAAA,OAAA,CAAQ,MAAO,CAAA,IAAI,CAAG,EAAA;AACxB,UAAK,IAAA,CAAA,IAAIC,8BAAwB,CAAA,gCAAgC,CAAC,CAAA;AAAA;AACpE,SACC,SAAS;AAAA,KACd;AAEA,IAAA,OAAA,CAAQ,IAAI,IAAI,CAAA;AAAA,GAClB;AACF;;;;;;"}
|
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
var http = require('http');
|
|
4
4
|
var https = require('https');
|
|
5
|
-
var stoppableServer = require('stoppable');
|
|
6
5
|
var getGeneratedCertificate = require('./getGeneratedCertificate.cjs.js');
|
|
7
6
|
|
|
8
|
-
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
9
|
-
|
|
10
7
|
function _interopNamespaceCompat(e) {
|
|
11
8
|
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
12
9
|
var n = Object.create(null);
|
|
@@ -27,12 +24,9 @@ function _interopNamespaceCompat(e) {
|
|
|
27
24
|
|
|
28
25
|
var http__namespace = /*#__PURE__*/_interopNamespaceCompat(http);
|
|
29
26
|
var https__namespace = /*#__PURE__*/_interopNamespaceCompat(https);
|
|
30
|
-
var stoppableServer__default = /*#__PURE__*/_interopDefaultCompat(stoppableServer);
|
|
31
27
|
|
|
32
28
|
async function createHttpServer(listener, options, deps) {
|
|
33
29
|
const server = await createServer(listener, options, deps);
|
|
34
|
-
const stopper = stoppableServer__default.default(server, 0);
|
|
35
|
-
const stopServer = stopper.stop.bind(stopper);
|
|
36
30
|
return Object.assign(server, {
|
|
37
31
|
start() {
|
|
38
32
|
return new Promise((resolve, reject) => {
|
|
@@ -51,7 +45,7 @@ async function createHttpServer(listener, options, deps) {
|
|
|
51
45
|
},
|
|
52
46
|
stop() {
|
|
53
47
|
return new Promise((resolve, reject) => {
|
|
54
|
-
|
|
48
|
+
server.close((error) => {
|
|
55
49
|
if (error) {
|
|
56
50
|
reject(error);
|
|
57
51
|
} else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createHttpServer.cjs.js","sources":["../../../../src/entrypoints/rootHttpRouter/http/createHttpServer.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 * as http from 'http';\nimport * as https from 'https';\nimport
|
|
1
|
+
{"version":3,"file":"createHttpServer.cjs.js","sources":["../../../../src/entrypoints/rootHttpRouter/http/createHttpServer.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 * as http from 'http';\nimport * as https from 'https';\nimport { RequestListener } from 'http';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { HttpServerOptions, ExtendedHttpServer } from './types';\nimport { getGeneratedCertificate } from './getGeneratedCertificate';\n\n/**\n * Creates a Node.js HTTP or HTTPS server instance.\n *\n * @public\n */\nexport async function createHttpServer(\n listener: RequestListener,\n options: HttpServerOptions,\n deps: { logger: LoggerService },\n): Promise<ExtendedHttpServer> {\n const server = await createServer(listener, options, deps);\n return Object.assign(server, {\n start() {\n return new Promise<void>((resolve, reject) => {\n const handleStartupError = (error: Error) => {\n server.close();\n reject(error);\n };\n\n server.on('error', handleStartupError);\n\n const { host, port } = options.listen;\n server.listen(port, host, () => {\n server.off('error', handleStartupError);\n deps.logger.info(`Listening on ${host}:${port}`);\n resolve();\n });\n });\n },\n\n stop() {\n return new Promise<void>((resolve, reject) => {\n server.close(error => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n },\n\n port() {\n const address = server.address();\n if (typeof address === 'string' || address === null) {\n throw new Error(`Unexpected server address '${address}'`);\n }\n return address.port;\n },\n });\n}\n\nasync function createServer(\n listener: RequestListener,\n options: HttpServerOptions,\n deps: { logger: LoggerService },\n): Promise<http.Server> {\n if (options.https) {\n const { certificate } = options.https;\n if (certificate.type === 'generated') {\n const credentials = await getGeneratedCertificate(\n certificate.hostname,\n deps.logger,\n );\n return https.createServer(credentials, listener);\n }\n return https.createServer(certificate, listener);\n }\n\n return http.createServer(listener);\n}\n"],"names":["getGeneratedCertificate","https","http"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BsB,eAAA,gBAAA,CACpB,QACA,EAAA,OAAA,EACA,IAC6B,EAAA;AAC7B,EAAA,MAAM,MAAS,GAAA,MAAM,YAAa,CAAA,QAAA,EAAU,SAAS,IAAI,CAAA;AACzD,EAAO,OAAA,MAAA,CAAO,OAAO,MAAQ,EAAA;AAAA,IAC3B,KAAQ,GAAA;AACN,MAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAW,KAAA;AAC5C,QAAM,MAAA,kBAAA,GAAqB,CAAC,KAAiB,KAAA;AAC3C,UAAA,MAAA,CAAO,KAAM,EAAA;AACb,UAAA,MAAA,CAAO,KAAK,CAAA;AAAA,SACd;AAEA,QAAO,MAAA,CAAA,EAAA,CAAG,SAAS,kBAAkB,CAAA;AAErC,QAAA,MAAM,EAAE,IAAA,EAAM,IAAK,EAAA,GAAI,OAAQ,CAAA,MAAA;AAC/B,QAAO,MAAA,CAAA,MAAA,CAAO,IAAM,EAAA,IAAA,EAAM,MAAM;AAC9B,UAAO,MAAA,CAAA,GAAA,CAAI,SAAS,kBAAkB,CAAA;AACtC,UAAA,IAAA,CAAK,OAAO,IAAK,CAAA,CAAA,aAAA,EAAgB,IAAI,CAAA,CAAA,EAAI,IAAI,CAAE,CAAA,CAAA;AAC/C,UAAQ,OAAA,EAAA;AAAA,SACT,CAAA;AAAA,OACF,CAAA;AAAA,KACH;AAAA,IAEA,IAAO,GAAA;AACL,MAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAW,KAAA;AAC5C,QAAA,MAAA,CAAO,MAAM,CAAS,KAAA,KAAA;AACpB,UAAA,IAAI,KAAO,EAAA;AACT,YAAA,MAAA,CAAO,KAAK,CAAA;AAAA,WACP,MAAA;AACL,YAAQ,OAAA,EAAA;AAAA;AACV,SACD,CAAA;AAAA,OACF,CAAA;AAAA,KACH;AAAA,IAEA,IAAO,GAAA;AACL,MAAM,MAAA,OAAA,GAAU,OAAO,OAAQ,EAAA;AAC/B,MAAA,IAAI,OAAO,OAAA,KAAY,QAAY,IAAA,OAAA,KAAY,IAAM,EAAA;AACnD,QAAA,MAAM,IAAI,KAAA,CAAM,CAA8B,2BAAA,EAAA,OAAO,CAAG,CAAA,CAAA,CAAA;AAAA;AAE1D,MAAA,OAAO,OAAQ,CAAA,IAAA;AAAA;AACjB,GACD,CAAA;AACH;AAEA,eAAe,YAAA,CACb,QACA,EAAA,OAAA,EACA,IACsB,EAAA;AACtB,EAAA,IAAI,QAAQ,KAAO,EAAA;AACjB,IAAM,MAAA,EAAE,WAAY,EAAA,GAAI,OAAQ,CAAA,KAAA;AAChC,IAAI,IAAA,WAAA,CAAY,SAAS,WAAa,EAAA;AACpC,MAAA,MAAM,cAAc,MAAMA,+CAAA;AAAA,QACxB,WAAY,CAAA,QAAA;AAAA,QACZ,IAAK,CAAA;AAAA,OACP;AACA,MAAO,OAAAC,gBAAA,CAAM,YAAa,CAAA,WAAA,EAAa,QAAQ,CAAA;AAAA;AAEjD,IAAO,OAAAA,gBAAA,CAAM,YAAa,CAAA,WAAA,EAAa,QAAQ,CAAA;AAAA;AAGjD,EAAO,OAAAC,eAAA,CAAK,aAAa,QAAQ,CAAA;AACnC;;;;"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
4
|
var express = require('express');
|
|
5
|
-
var config = require('./http/config.cjs.js');
|
|
5
|
+
var config$1 = require('./http/config.cjs.js');
|
|
6
6
|
var createHttpServer = require('./http/createHttpServer.cjs.js');
|
|
7
7
|
var MiddlewareFactory = require('./http/MiddlewareFactory.cjs.js');
|
|
8
8
|
require('minimatch');
|
|
@@ -10,6 +10,8 @@ require('helmet');
|
|
|
10
10
|
require('lodash/kebabCase');
|
|
11
11
|
var DefaultRootHttpRouter = require('./DefaultRootHttpRouter.cjs.js');
|
|
12
12
|
var createHealthRouter = require('./createHealthRouter.cjs.js');
|
|
13
|
+
var createLifecycleMiddleware = require('./createLifecycleMiddleware.cjs.js');
|
|
14
|
+
var config = require('@backstage/config');
|
|
13
15
|
|
|
14
16
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
15
17
|
|
|
@@ -26,17 +28,34 @@ const rootHttpRouterServiceFactoryWithOptions = (options) => backendPluginApi.cr
|
|
|
26
28
|
lifecycle: backendPluginApi.coreServices.rootLifecycle,
|
|
27
29
|
health: backendPluginApi.coreServices.rootHealth
|
|
28
30
|
},
|
|
29
|
-
async factory({ config: config$
|
|
31
|
+
async factory({ config: config$2, rootLogger, lifecycle, health }) {
|
|
30
32
|
const { indexPath, configure = defaultConfigure } = options ?? {};
|
|
31
33
|
const logger = rootLogger.child({ service: "rootHttpRouter" });
|
|
32
34
|
const app = express__default.default();
|
|
33
35
|
const router = DefaultRootHttpRouter.DefaultRootHttpRouter.create({ indexPath });
|
|
34
|
-
const middleware = MiddlewareFactory.MiddlewareFactory.create({ config: config$
|
|
36
|
+
const middleware = MiddlewareFactory.MiddlewareFactory.create({ config: config$2, logger });
|
|
35
37
|
const routes = router.handler();
|
|
36
|
-
const healthRouter = createHealthRouter.createHealthRouter({ health });
|
|
38
|
+
const healthRouter = createHealthRouter.createHealthRouter({ config: config$2, health });
|
|
39
|
+
let startupRequestPauseTimeout;
|
|
40
|
+
if (config$2.has("backend.lifecycle.startupRequestPauseTimeout")) {
|
|
41
|
+
startupRequestPauseTimeout = config.readDurationFromConfig(config$2, {
|
|
42
|
+
key: "backend.lifecycle.startupRequestPauseTimeout"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
let serverShutdownDelay;
|
|
46
|
+
if (config$2.has("backend.lifecycle.serverShutdownDelay")) {
|
|
47
|
+
serverShutdownDelay = config.readDurationFromConfig(config$2, {
|
|
48
|
+
key: "backend.lifecycle.serverShutdownDelay"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const lifecycleMiddleware = createLifecycleMiddleware.createLifecycleMiddleware({
|
|
52
|
+
lifecycle,
|
|
53
|
+
startupRequestPauseTimeout,
|
|
54
|
+
serverShutdownDelay
|
|
55
|
+
});
|
|
37
56
|
const server = await createHttpServer.createHttpServer(
|
|
38
57
|
app,
|
|
39
|
-
config.readHttpServerOptions(config$
|
|
58
|
+
config$1.readHttpServerOptions(config$2.getOptionalConfig("backend")),
|
|
40
59
|
{ logger }
|
|
41
60
|
);
|
|
42
61
|
configure({
|
|
@@ -44,10 +63,11 @@ const rootHttpRouterServiceFactoryWithOptions = (options) => backendPluginApi.cr
|
|
|
44
63
|
server,
|
|
45
64
|
routes,
|
|
46
65
|
middleware,
|
|
47
|
-
config: config$
|
|
66
|
+
config: config$2,
|
|
48
67
|
logger,
|
|
49
68
|
lifecycle,
|
|
50
69
|
healthRouter,
|
|
70
|
+
lifecycleMiddleware,
|
|
51
71
|
applyDefaults() {
|
|
52
72
|
if (process.env.NODE_ENV === "development") {
|
|
53
73
|
app.set("json spaces", 2);
|
|
@@ -57,6 +77,7 @@ const rootHttpRouterServiceFactoryWithOptions = (options) => backendPluginApi.cr
|
|
|
57
77
|
app.use(middleware.compression());
|
|
58
78
|
app.use(middleware.logging());
|
|
59
79
|
app.use(healthRouter);
|
|
80
|
+
app.use(lifecycleMiddleware);
|
|
60
81
|
app.use(routes);
|
|
61
82
|
app.use(middleware.notFound());
|
|
62
83
|
app.use(middleware.error());
|
|
@@ -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';\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({ health });\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 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"],"mappings":"
|
|
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 { createLifecycleMiddleware } from './createLifecycleMiddleware';\nimport { readDurationFromConfig } from '@backstage/config';\nimport { HumanDuration } from '@backstage/types';\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 lifecycleMiddleware: 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 let startupRequestPauseTimeout: HumanDuration | undefined;\n if (config.has('backend.lifecycle.startupRequestPauseTimeout')) {\n startupRequestPauseTimeout = readDurationFromConfig(config, {\n key: 'backend.lifecycle.startupRequestPauseTimeout',\n });\n }\n\n let serverShutdownDelay: HumanDuration | undefined;\n if (config.has('backend.lifecycle.serverShutdownDelay')) {\n serverShutdownDelay = readDurationFromConfig(config, {\n key: 'backend.lifecycle.serverShutdownDelay',\n });\n }\n\n const lifecycleMiddleware = createLifecycleMiddleware({\n lifecycle,\n startupRequestPauseTimeout,\n serverShutdownDelay,\n });\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 lifecycleMiddleware,\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(lifecycleMiddleware);\n app.use(routes);\n app.use(middleware.notFound());\n app.use(middleware.error());\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","readDurationFromConfig","createLifecycleMiddleware","createHttpServer","readHttpServerOptions"],"mappings":"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"}
|
|
@@ -32,6 +32,35 @@ class BackendLifecycleImpl {
|
|
|
32
32
|
})
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
|
+
#hasBeforeShutdown = false;
|
|
36
|
+
#beforeShutdownTasks = [];
|
|
37
|
+
addBeforeShutdownHook(hook) {
|
|
38
|
+
if (this.#hasBeforeShutdown) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"Attempt to add before shutdown hook after shutdown has started"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
this.#beforeShutdownTasks.push({ hook });
|
|
44
|
+
}
|
|
45
|
+
async beforeShutdown() {
|
|
46
|
+
if (this.#hasBeforeShutdown) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.#hasBeforeShutdown = true;
|
|
50
|
+
this.logger.debug(
|
|
51
|
+
`Running ${this.#beforeShutdownTasks.length} before shutdown tasks...`
|
|
52
|
+
);
|
|
53
|
+
await Promise.all(
|
|
54
|
+
this.#beforeShutdownTasks.map(async ({ hook }) => {
|
|
55
|
+
try {
|
|
56
|
+
await hook();
|
|
57
|
+
this.logger.debug(`Before shutdown hook succeeded`);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
this.logger.error(`Before shutdown hook failed, ${error}`);
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
}
|
|
35
64
|
#hasShutdown = false;
|
|
36
65
|
#shutdownTasks = [];
|
|
37
66
|
addShutdownHook(hook, options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rootLifecycleServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.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 createServiceFactory,\n coreServices,\n LifecycleServiceStartupHook,\n LifecycleServiceStartupOptions,\n LifecycleServiceShutdownHook,\n LifecycleServiceShutdownOptions,\n RootLifecycleService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport class BackendLifecycleImpl implements RootLifecycleService {\n constructor(private readonly logger: LoggerService) {}\n\n #hasStarted = false;\n #startupTasks: Array<{\n hook: LifecycleServiceStartupHook;\n options?: LifecycleServiceStartupOptions;\n }> = [];\n\n addStartupHook(\n hook: LifecycleServiceStartupHook,\n options?: LifecycleServiceStartupOptions,\n ): void {\n if (this.#hasStarted) {\n throw new Error('Attempted to add startup hook after startup');\n }\n this.#startupTasks.push({ hook, options });\n }\n\n async startup(): Promise<void> {\n if (this.#hasStarted) {\n return;\n }\n this.#hasStarted = true;\n\n this.logger.debug(`Running ${this.#startupTasks.length} startup tasks...`);\n await Promise.all(\n this.#startupTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Startup hook succeeded`);\n } catch (error) {\n logger.error(`Startup hook failed, ${error}`);\n }\n }),\n );\n }\n\n #hasShutdown = false;\n #shutdownTasks: Array<{\n hook: LifecycleServiceShutdownHook;\n options?: LifecycleServiceShutdownOptions;\n }> = [];\n\n addShutdownHook(\n hook: LifecycleServiceShutdownHook,\n options?: LifecycleServiceShutdownOptions,\n ): void {\n if (this.#hasShutdown) {\n throw new Error('Attempted to add shutdown hook after shutdown');\n }\n this.#shutdownTasks.push({ hook, options });\n }\n\n async shutdown(): Promise<void> {\n if (this.#hasShutdown) {\n return;\n }\n this.#hasShutdown = true;\n\n this.logger.debug(\n `Running ${this.#shutdownTasks.length} shutdown tasks...`,\n );\n await Promise.all(\n this.#shutdownTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Shutdown hook succeeded`);\n } catch (error) {\n logger.error(`Shutdown hook failed, ${error}`);\n }\n }),\n );\n }\n}\n\n/**\n * Registration of backend startup and shutdown lifecycle hooks.\n *\n * See {@link @backstage/code-plugin-api#RootLifecycleService}\n * and {@link https://backstage.io/docs/backend-system/core-services/root-lifecycle | the service docs}\n * for more information.\n *\n * @public\n */\nexport const rootLifecycleServiceFactory = createServiceFactory({\n service: coreServices.rootLifecycle,\n deps: {\n logger: coreServices.rootLogger,\n },\n async factory({ logger }) {\n return new BackendLifecycleImpl(logger);\n },\n});\n"],"names":["createServiceFactory","coreServices"],"mappings":";;;;AA4BO,MAAM,oBAAqD,CAAA;AAAA,EAChE,YAA6B,MAAuB,EAAA;AAAvB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA;AAAwB,EAErD,WAAc,GAAA,KAAA;AAAA,EACd,gBAGK,EAAC;AAAA,EAEN,cAAA,CACE,MACA,OACM,EAAA;AACN,IAAA,IAAI,KAAK,WAAa,EAAA;AACpB,MAAM,MAAA,IAAI,MAAM,6CAA6C,CAAA;AAAA;AAE/D,IAAA,IAAA,CAAK,aAAc,CAAA,IAAA,CAAK,EAAE,IAAA,EAAM,SAAS,CAAA;AAAA;AAC3C,EAEA,MAAM,OAAyB,GAAA;AAC7B,IAAA,IAAI,KAAK,WAAa,EAAA;AACpB,MAAA;AAAA;AAEF,IAAA,IAAA,CAAK,WAAc,GAAA,IAAA;AAEnB,IAAA,IAAA,CAAK,OAAO,KAAM,CAAA,CAAA,QAAA,EAAW,IAAK,CAAA,aAAA,CAAc,MAAM,CAAmB,iBAAA,CAAA,CAAA;AACzE,IAAA,MAAM,OAAQ,CAAA,GAAA;AAAA,MACZ,KAAK,aAAc,CAAA,GAAA,CAAI,OAAO,EAAE,IAAA,EAAM,SAAc,KAAA;AAClD,QAAM,MAAA,MAAA,GAAS,OAAS,EAAA,MAAA,IAAU,IAAK,CAAA,MAAA;AACvC,QAAI,IAAA;AACF,UAAA,MAAM,IAAK,EAAA;AACX,UAAA,MAAA,CAAO,MAAM,CAAwB,sBAAA,CAAA,CAAA;AAAA,iBAC9B,KAAO,EAAA;AACd,UAAO,MAAA,CAAA,KAAA,CAAM,CAAwB,qBAAA,EAAA,KAAK,CAAE,CAAA,CAAA;AAAA;AAC9C,OACD;AAAA,KACH;AAAA;AACF,EAEA,YAAe,GAAA,KAAA;AAAA,EACf,iBAGK,EAAC;AAAA,EAEN,eAAA,CACE,MACA,OACM,EAAA;AACN,IAAA,IAAI,KAAK,YAAc,EAAA;AACrB,MAAM,MAAA,IAAI,MAAM,+CAA+C,CAAA;AAAA;AAEjE,IAAA,IAAA,CAAK,cAAe,CAAA,IAAA,CAAK,EAAE,IAAA,EAAM,SAAS,CAAA;AAAA;AAC5C,EAEA,MAAM,QAA0B,GAAA;AAC9B,IAAA,IAAI,KAAK,YAAc,EAAA;AACrB,MAAA;AAAA;AAEF,IAAA,IAAA,CAAK,YAAe,GAAA,IAAA;AAEpB,IAAA,IAAA,CAAK,MAAO,CAAA,KAAA;AAAA,MACV,CAAA,QAAA,EAAW,IAAK,CAAA,cAAA,CAAe,MAAM,CAAA,kBAAA;AAAA,KACvC;AACA,IAAA,MAAM,OAAQ,CAAA,GAAA;AAAA,MACZ,KAAK,cAAe,CAAA,GAAA,CAAI,OAAO,EAAE,IAAA,EAAM,SAAc,KAAA;AACnD,QAAM,MAAA,MAAA,GAAS,OAAS,EAAA,MAAA,IAAU,IAAK,CAAA,MAAA;AACvC,QAAI,IAAA;AACF,UAAA,MAAM,IAAK,EAAA;AACX,UAAA,MAAA,CAAO,MAAM,CAAyB,uBAAA,CAAA,CAAA;AAAA,iBAC/B,KAAO,EAAA;AACd,UAAO,MAAA,CAAA,KAAA,CAAM,CAAyB,sBAAA,EAAA,KAAK,CAAE,CAAA,CAAA;AAAA;AAC/C,OACD;AAAA,KACH;AAAA;AAEJ;AAWO,MAAM,8BAA8BA,qCAAqB,CAAA;AAAA,EAC9D,SAASC,6BAAa,CAAA,aAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,MAAA,EAAU,EAAA;AACxB,IAAO,OAAA,IAAI,qBAAqB,MAAM,CAAA;AAAA;AAE1C,CAAC;;;;;"}
|
|
1
|
+
{"version":3,"file":"rootLifecycleServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.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 createServiceFactory,\n coreServices,\n LifecycleServiceStartupHook,\n LifecycleServiceStartupOptions,\n LifecycleServiceShutdownHook,\n LifecycleServiceShutdownOptions,\n RootLifecycleService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport class BackendLifecycleImpl implements RootLifecycleService {\n constructor(private readonly logger: LoggerService) {}\n\n #hasStarted = false;\n #startupTasks: Array<{\n hook: LifecycleServiceStartupHook;\n options?: LifecycleServiceStartupOptions;\n }> = [];\n\n addStartupHook(\n hook: LifecycleServiceStartupHook,\n options?: LifecycleServiceStartupOptions,\n ): void {\n if (this.#hasStarted) {\n throw new Error('Attempted to add startup hook after startup');\n }\n this.#startupTasks.push({ hook, options });\n }\n\n async startup(): Promise<void> {\n if (this.#hasStarted) {\n return;\n }\n this.#hasStarted = true;\n\n this.logger.debug(`Running ${this.#startupTasks.length} startup tasks...`);\n await Promise.all(\n this.#startupTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Startup hook succeeded`);\n } catch (error) {\n logger.error(`Startup hook failed, ${error}`);\n }\n }),\n );\n }\n\n #hasBeforeShutdown = false;\n #beforeShutdownTasks: Array<{ hook: () => void | Promise<void> }> = [];\n\n addBeforeShutdownHook(hook: () => void): void {\n if (this.#hasBeforeShutdown) {\n throw new Error(\n 'Attempt to add before shutdown hook after shutdown has started',\n );\n }\n this.#beforeShutdownTasks.push({ hook });\n }\n\n async beforeShutdown(): Promise<void> {\n if (this.#hasBeforeShutdown) {\n return;\n }\n this.#hasBeforeShutdown = true;\n\n this.logger.debug(\n `Running ${this.#beforeShutdownTasks.length} before shutdown tasks...`,\n );\n await Promise.all(\n this.#beforeShutdownTasks.map(async ({ hook }) => {\n try {\n await hook();\n this.logger.debug(`Before shutdown hook succeeded`);\n } catch (error) {\n this.logger.error(`Before shutdown hook failed, ${error}`);\n }\n }),\n );\n }\n\n #hasShutdown = false;\n #shutdownTasks: Array<{\n hook: LifecycleServiceShutdownHook;\n options?: LifecycleServiceShutdownOptions;\n }> = [];\n\n addShutdownHook(\n hook: LifecycleServiceShutdownHook,\n options?: LifecycleServiceShutdownOptions,\n ): void {\n if (this.#hasShutdown) {\n throw new Error('Attempted to add shutdown hook after shutdown');\n }\n this.#shutdownTasks.push({ hook, options });\n }\n\n async shutdown(): Promise<void> {\n if (this.#hasShutdown) {\n return;\n }\n this.#hasShutdown = true;\n\n this.logger.debug(\n `Running ${this.#shutdownTasks.length} shutdown tasks...`,\n );\n await Promise.all(\n this.#shutdownTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Shutdown hook succeeded`);\n } catch (error) {\n logger.error(`Shutdown hook failed, ${error}`);\n }\n }),\n );\n }\n}\n\n/**\n * Registration of backend startup and shutdown lifecycle hooks.\n *\n * See {@link @backstage/code-plugin-api#RootLifecycleService}\n * and {@link https://backstage.io/docs/backend-system/core-services/root-lifecycle | the service docs}\n * for more information.\n *\n * @public\n */\nexport const rootLifecycleServiceFactory = createServiceFactory({\n service: coreServices.rootLifecycle,\n deps: {\n logger: coreServices.rootLogger,\n },\n async factory({ logger }) {\n return new BackendLifecycleImpl(logger);\n },\n});\n"],"names":["createServiceFactory","coreServices"],"mappings":"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"}
|
|
@@ -6,11 +6,22 @@ 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');
|
|
9
10
|
|
|
10
11
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
11
12
|
|
|
12
13
|
var parseGitUrl__default = /*#__PURE__*/_interopDefaultCompat(parseGitUrl);
|
|
14
|
+
var pThrottle__default = /*#__PURE__*/_interopDefaultCompat(pThrottle);
|
|
13
15
|
|
|
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
|
+
);
|
|
14
25
|
class BitbucketServerUrlReader {
|
|
15
26
|
constructor(integration, deps) {
|
|
16
27
|
this.integration = integration;
|
|
@@ -41,7 +52,7 @@ class BitbucketServerUrlReader {
|
|
|
41
52
|
);
|
|
42
53
|
let response;
|
|
43
54
|
try {
|
|
44
|
-
response = await
|
|
55
|
+
response = await throttledFetch(bitbucketUrl.toString(), {
|
|
45
56
|
headers: {
|
|
46
57
|
...requestOptions.headers,
|
|
47
58
|
...etag && { "If-None-Match": etag },
|
|
@@ -82,7 +93,7 @@ class BitbucketServerUrlReader {
|
|
|
82
93
|
url,
|
|
83
94
|
this.integration.config
|
|
84
95
|
);
|
|
85
|
-
const archiveResponse = await
|
|
96
|
+
const archiveResponse = await throttledFetch(
|
|
86
97
|
downloadUrl,
|
|
87
98
|
integration.getBitbucketServerRequestOptions(this.integration.config)
|
|
88
99
|
);
|
|
@@ -130,7 +141,7 @@ class BitbucketServerUrlReader {
|
|
|
130
141
|
const { name: repoName, owner: project, ref: branch } = parseGitUrl__default.default(url);
|
|
131
142
|
const branchParameter = branch ? `?filterText=${encodeURIComponent(branch)}` : "/default";
|
|
132
143
|
const branchListUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches${branchParameter}`;
|
|
133
|
-
const branchListResponse = await
|
|
144
|
+
const branchListResponse = await throttledFetch(
|
|
134
145
|
branchListUrl,
|
|
135
146
|
integration.getBitbucketServerRequestOptions(this.integration.config)
|
|
136
147
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitbucketServerUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/BitbucketServerUrlReader.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 {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport { NotFoundError, NotModifiedError } from '@backstage/errors';\nimport {\n BitbucketServerIntegration,\n getBitbucketServerDownloadUrl,\n getBitbucketServerFileFetchUrl,\n getBitbucketServerRequestOptions,\n ScmIntegrations,\n} from '@backstage/integration';\nimport parseGitUrl from 'git-url-parse';\nimport { trimEnd } from 'lodash';\nimport { Minimatch } from 'minimatch';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket Server APIs.\n *\n * @public\n */\nexport class BitbucketServerUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n return integrations.bitbucketServer.list().map(integration => {\n const reader = new BitbucketServerUrlReader(integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) => url.host === integration.config.host;\n return { reader, predicate };\n });\n };\n\n constructor(\n private readonly integration: BitbucketServerIntegration,\n private readonly deps: { treeResponseFactory: ReadTreeResponseFactory },\n ) {}\n\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const { etag, lastModifiedAfter, signal } = options ?? {};\n const bitbucketUrl = getBitbucketServerFileFetchUrl(\n url,\n this.integration.config,\n );\n const requestOptions = getBitbucketServerRequestOptions(\n this.integration.config,\n );\n\n let response: Response;\n try {\n response = await fetch(bitbucketUrl.toString(), {\n headers: {\n ...requestOptions.headers,\n ...(etag && { 'If-None-Match': etag }),\n ...(lastModifiedAfter && {\n 'If-Modified-Since': lastModifiedAfter.toUTCString(),\n }),\n },\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can be\n // removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n ...(signal && { signal: signal as any }),\n });\n } catch (e) {\n throw new Error(`Unable to read ${url}, ${e}`);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n if (response.ok) {\n return ReadUrlResponseFactory.fromResponse(response);\n }\n\n const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n const { filepath } = parseGitUrl(url);\n\n const lastCommitShortHash = await this.getLastCommitShortHash(url);\n if (options?.etag && options.etag === lastCommitShortHash) {\n throw new NotModifiedError();\n }\n\n const downloadUrl = await getBitbucketServerDownloadUrl(\n url,\n this.integration.config,\n );\n const archiveResponse = await fetch(\n downloadUrl,\n getBitbucketServerRequestOptions(this.integration.config),\n );\n if (!archiveResponse.ok) {\n const message = `Failed to read tree from ${url}, ${archiveResponse.status} ${archiveResponse.statusText}`;\n if (archiveResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n return await this.deps.treeResponseFactory.fromTarArchive({\n response: archiveResponse,\n subpath: filepath,\n etag: lastCommitShortHash,\n filter: options?.filter,\n });\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { filepath } = parseGitUrl(url);\n const matcher = new Minimatch(filepath);\n\n // TODO(freben): For now, read the entire repo and filter through that. In\n // a future improvement, we could be smart and try to deduce that non-glob\n // prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used\n // to get just that part of the repo.\n const treeUrl = trimEnd(url.replace(filepath, ''), '/');\n\n const tree = await this.readTree(treeUrl, {\n etag: options?.etag,\n filter: path => matcher.match(path),\n });\n const files = await tree.files();\n\n return {\n etag: tree.etag,\n files: files.map(file => ({\n url: this.integration.resolveUrl({\n url: `/${file.path}`,\n base: url,\n }),\n content: file.content,\n lastModifiedAt: file.lastModifiedAt,\n })),\n };\n }\n\n toString() {\n const { host, token } = this.integration.config;\n const authed = Boolean(token);\n return `bitbucketServer{host=${host},authed=${authed}}`;\n }\n\n private async getLastCommitShortHash(url: string): Promise<string> {\n const { name: repoName, owner: project, ref: branch } = parseGitUrl(url);\n\n // If a branch is provided use that otherwise fall back to the default branch\n const branchParameter = branch\n ? `?filterText=${encodeURIComponent(branch)}`\n : '/default';\n\n // https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp211 (branches docs)\n const branchListUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches${branchParameter}`;\n\n const branchListResponse = await fetch(\n branchListUrl,\n getBitbucketServerRequestOptions(this.integration.config),\n );\n if (!branchListResponse.ok) {\n const message = `Failed to retrieve branch list from ${branchListUrl}, ${branchListResponse.status} ${branchListResponse.statusText}`;\n if (branchListResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n const branchMatches = await branchListResponse.json();\n\n if (branchMatches && branchMatches.size > 0) {\n const exactBranchMatch = branchMatches.values.filter(\n (branchDetails: { displayId: string }) =>\n branchDetails.displayId === branch,\n )[0];\n return exactBranchMatch.latestCommit.substring(0, 12);\n }\n\n // Handle when no branch is provided using the default as the fallback\n if (!branch && branchMatches) {\n return branchMatches.latestCommit.substring(0, 12);\n }\n\n throw new Error(\n `Failed to find Last Commit using ${\n branch ? `branch \"${branch}\"` : 'default branch'\n } in response from ${branchListUrl}`,\n );\n }\n}\n"],"names":["ScmIntegrations","getBitbucketServerFileFetchUrl","getBitbucketServerRequestOptions","NotModifiedError","ReadUrlResponseFactory","NotFoundError","parseGitUrl","getBitbucketServerDownloadUrl","Minimatch","trimEnd"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BitbucketServerUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/BitbucketServerUrlReader.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 {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport { NotFoundError, NotModifiedError } from '@backstage/errors';\nimport {\n BitbucketServerIntegration,\n getBitbucketServerDownloadUrl,\n getBitbucketServerFileFetchUrl,\n getBitbucketServerRequestOptions,\n ScmIntegrations,\n} from '@backstage/integration';\nimport parseGitUrl from 'git-url-parse';\nimport { trimEnd } from 'lodash';\nimport { Minimatch } from 'minimatch';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\n\nimport pThrottle from 'p-throttle';\n\n// 1 per second\nconst throttle = pThrottle({\n limit: 1,\n interval: 1000,\n});\n\nconst throttledFetch = throttle(\n async (url: RequestInfo, options?: RequestInit) => {\n return await fetch(url, options);\n },\n);\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket Server APIs.\n *\n * @public\n */\nexport class BitbucketServerUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n return integrations.bitbucketServer.list().map(integration => {\n const reader = new BitbucketServerUrlReader(integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) => url.host === integration.config.host;\n return { reader, predicate };\n });\n };\n\n constructor(\n private readonly integration: BitbucketServerIntegration,\n private readonly deps: { treeResponseFactory: ReadTreeResponseFactory },\n ) {}\n\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const { etag, lastModifiedAfter, signal } = options ?? {};\n const bitbucketUrl = getBitbucketServerFileFetchUrl(\n url,\n this.integration.config,\n );\n const requestOptions = getBitbucketServerRequestOptions(\n this.integration.config,\n );\n\n let response: Response;\n try {\n response = await throttledFetch(bitbucketUrl.toString(), {\n headers: {\n ...requestOptions.headers,\n ...(etag && { 'If-None-Match': etag }),\n ...(lastModifiedAfter && {\n 'If-Modified-Since': lastModifiedAfter.toUTCString(),\n }),\n },\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can be\n // removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n ...(signal && { signal: signal as any }),\n });\n } catch (e) {\n throw new Error(`Unable to read ${url}, ${e}`);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n if (response.ok) {\n return ReadUrlResponseFactory.fromResponse(response);\n }\n\n const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n const { filepath } = parseGitUrl(url);\n\n const lastCommitShortHash = await this.getLastCommitShortHash(url);\n if (options?.etag && options.etag === lastCommitShortHash) {\n throw new NotModifiedError();\n }\n\n const downloadUrl = await getBitbucketServerDownloadUrl(\n url,\n this.integration.config,\n );\n const archiveResponse = await throttledFetch(\n downloadUrl,\n getBitbucketServerRequestOptions(this.integration.config),\n );\n if (!archiveResponse.ok) {\n const message = `Failed to read tree from ${url}, ${archiveResponse.status} ${archiveResponse.statusText}`;\n if (archiveResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n return await this.deps.treeResponseFactory.fromTarArchive({\n response: archiveResponse,\n subpath: filepath,\n etag: lastCommitShortHash,\n filter: options?.filter,\n });\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { filepath } = parseGitUrl(url);\n const matcher = new Minimatch(filepath);\n\n // TODO(freben): For now, read the entire repo and filter through that. In\n // a future improvement, we could be smart and try to deduce that non-glob\n // prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used\n // to get just that part of the repo.\n const treeUrl = trimEnd(url.replace(filepath, ''), '/');\n\n const tree = await this.readTree(treeUrl, {\n etag: options?.etag,\n filter: path => matcher.match(path),\n });\n const files = await tree.files();\n\n return {\n etag: tree.etag,\n files: files.map(file => ({\n url: this.integration.resolveUrl({\n url: `/${file.path}`,\n base: url,\n }),\n content: file.content,\n lastModifiedAt: file.lastModifiedAt,\n })),\n };\n }\n\n toString() {\n const { host, token } = this.integration.config;\n const authed = Boolean(token);\n return `bitbucketServer{host=${host},authed=${authed}}`;\n }\n\n private async getLastCommitShortHash(url: string): Promise<string> {\n const { name: repoName, owner: project, ref: branch } = parseGitUrl(url);\n\n // If a branch is provided use that otherwise fall back to the default branch\n const branchParameter = branch\n ? `?filterText=${encodeURIComponent(branch)}`\n : '/default';\n\n // https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp211 (branches docs)\n const branchListUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches${branchParameter}`;\n\n const branchListResponse = await throttledFetch(\n branchListUrl,\n getBitbucketServerRequestOptions(this.integration.config),\n );\n if (!branchListResponse.ok) {\n const message = `Failed to retrieve branch list from ${branchListUrl}, ${branchListResponse.status} ${branchListResponse.statusText}`;\n if (branchListResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n const branchMatches = await branchListResponse.json();\n\n if (branchMatches && branchMatches.size > 0) {\n const exactBranchMatch = branchMatches.values.filter(\n (branchDetails: { displayId: string }) =>\n branchDetails.displayId === branch,\n )[0];\n return exactBranchMatch.latestCommit.substring(0, 12);\n }\n\n // Handle when no branch is provided using the default as the fallback\n if (!branch && branchMatches) {\n return branchMatches.latestCommit.substring(0, 12);\n }\n\n throw new Error(\n `Failed to find Last Commit using ${\n branch ? `branch \"${branch}\"` : 'default branch'\n } in response from ${branchListUrl}`,\n );\n }\n}\n"],"names":["pThrottle","ScmIntegrations","getBitbucketServerFileFetchUrl","getBitbucketServerRequestOptions","NotModifiedError","ReadUrlResponseFactory","NotFoundError","parseGitUrl","getBitbucketServerDownloadUrl","Minimatch","trimEnd"],"mappings":"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"}
|
package/dist/package.json.cjs.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var name = "@backstage/backend-defaults";
|
|
6
|
-
var version = "0.6.0-next.
|
|
6
|
+
var version = "0.6.0-next.2";
|
|
7
7
|
var description = "Backend defaults used by Backstage backend apps";
|
|
8
8
|
var backstage = {
|
|
9
9
|
role: "node-library"
|
|
@@ -143,7 +143,7 @@ var dependencies = {
|
|
|
143
143
|
"@keyv/redis": "^4.0.1",
|
|
144
144
|
"@manypkg/get-packages": "^1.1.3",
|
|
145
145
|
"@octokit/rest": "^19.0.3",
|
|
146
|
-
"@opentelemetry/api": "^1.
|
|
146
|
+
"@opentelemetry/api": "^1.9.0",
|
|
147
147
|
"@types/cors": "^2.8.6",
|
|
148
148
|
"@types/express": "^4.17.6",
|
|
149
149
|
archiver: "^7.0.0",
|
|
@@ -172,13 +172,13 @@ var dependencies = {
|
|
|
172
172
|
"node-fetch": "^2.7.0",
|
|
173
173
|
"node-forge": "^1.3.1",
|
|
174
174
|
"p-limit": "^3.1.0",
|
|
175
|
+
"p-throttle": "^4.1.1",
|
|
175
176
|
"path-to-regexp": "^8.0.0",
|
|
176
177
|
pg: "^8.11.3",
|
|
177
178
|
"pg-connection-string": "^2.3.0",
|
|
178
179
|
"pg-format": "^1.0.4",
|
|
179
180
|
"raw-body": "^2.4.1",
|
|
180
181
|
selfsigned: "^2.0.0",
|
|
181
|
-
stoppable: "^1.1.0",
|
|
182
182
|
tar: "^6.1.12",
|
|
183
183
|
"triple-beam": "^1.4.1",
|
|
184
184
|
uuid: "^11.0.0",
|
package/dist/rootHttpRouter.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="express" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
4
|
-
import { RootHttpRouterService, RootHealthService,
|
|
4
|
+
import { RootHttpRouterService, RootHealthService, RootConfigService, LoggerService, LifecycleService } from '@backstage/backend-plugin-api';
|
|
5
5
|
import * as express from 'express';
|
|
6
6
|
import { Handler, RequestHandler, ErrorRequestHandler, Express } from 'express';
|
|
7
7
|
import { Config } from '@backstage/config';
|
|
@@ -42,6 +42,7 @@ declare class DefaultRootHttpRouter implements RootHttpRouterService {
|
|
|
42
42
|
*/
|
|
43
43
|
declare function createHealthRouter(options: {
|
|
44
44
|
health: RootHealthService;
|
|
45
|
+
config: RootConfigService;
|
|
45
46
|
}): express.Router;
|
|
46
47
|
|
|
47
48
|
/**
|
|
@@ -268,6 +269,7 @@ interface RootHttpRouterConfigureContext {
|
|
|
268
269
|
logger: LoggerService;
|
|
269
270
|
lifecycle: LifecycleService;
|
|
270
271
|
healthRouter: RequestHandler;
|
|
272
|
+
lifecycleMiddleware: RequestHandler;
|
|
271
273
|
applyDefaults: () => void;
|
|
272
274
|
}
|
|
273
275
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/backend-defaults",
|
|
3
|
-
"version": "0.6.0-next.
|
|
3
|
+
"version": "0.6.0-next.2",
|
|
4
4
|
"description": "Backend defaults used by Backstage backend apps",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "node-library"
|
|
@@ -193,26 +193,26 @@
|
|
|
193
193
|
"@aws-sdk/types": "^3.347.0",
|
|
194
194
|
"@azure/identity": "^4.0.0",
|
|
195
195
|
"@azure/storage-blob": "^12.5.0",
|
|
196
|
-
"@backstage/backend-app-api": "1.1.0-next.
|
|
196
|
+
"@backstage/backend-app-api": "1.1.0-next.2",
|
|
197
197
|
"@backstage/backend-dev-utils": "0.1.5",
|
|
198
|
-
"@backstage/backend-plugin-api": "1.1.0-next.
|
|
198
|
+
"@backstage/backend-plugin-api": "1.1.0-next.2",
|
|
199
199
|
"@backstage/cli-common": "0.1.15",
|
|
200
|
-
"@backstage/cli-node": "0.2.11-next.
|
|
201
|
-
"@backstage/config": "1.3.0",
|
|
202
|
-
"@backstage/config-loader": "1.9.3-next.
|
|
203
|
-
"@backstage/errors": "1.2.
|
|
204
|
-
"@backstage/integration": "1.16.0-next.
|
|
205
|
-
"@backstage/integration-aws-node": "0.1.
|
|
206
|
-
"@backstage/plugin-auth-node": "0.5.5-next.
|
|
207
|
-
"@backstage/plugin-events-node": "0.4.6-next.
|
|
208
|
-
"@backstage/plugin-permission-node": "0.8.6-next.
|
|
200
|
+
"@backstage/cli-node": "0.2.11-next.1",
|
|
201
|
+
"@backstage/config": "1.3.1-next.0",
|
|
202
|
+
"@backstage/config-loader": "1.9.3-next.1",
|
|
203
|
+
"@backstage/errors": "1.2.6-next.0",
|
|
204
|
+
"@backstage/integration": "1.16.0-next.1",
|
|
205
|
+
"@backstage/integration-aws-node": "0.1.14-next.0",
|
|
206
|
+
"@backstage/plugin-auth-node": "0.5.5-next.2",
|
|
207
|
+
"@backstage/plugin-events-node": "0.4.6-next.2",
|
|
208
|
+
"@backstage/plugin-permission-node": "0.8.6-next.2",
|
|
209
209
|
"@backstage/types": "1.2.0",
|
|
210
210
|
"@google-cloud/storage": "^7.0.0",
|
|
211
211
|
"@keyv/memcache": "^2.0.1",
|
|
212
212
|
"@keyv/redis": "^4.0.1",
|
|
213
213
|
"@manypkg/get-packages": "^1.1.3",
|
|
214
214
|
"@octokit/rest": "^19.0.3",
|
|
215
|
-
"@opentelemetry/api": "^1.
|
|
215
|
+
"@opentelemetry/api": "^1.9.0",
|
|
216
216
|
"@types/cors": "^2.8.6",
|
|
217
217
|
"@types/express": "^4.17.6",
|
|
218
218
|
"archiver": "^7.0.0",
|
|
@@ -241,13 +241,13 @@
|
|
|
241
241
|
"node-fetch": "^2.7.0",
|
|
242
242
|
"node-forge": "^1.3.1",
|
|
243
243
|
"p-limit": "^3.1.0",
|
|
244
|
+
"p-throttle": "^4.1.1",
|
|
244
245
|
"path-to-regexp": "^8.0.0",
|
|
245
246
|
"pg": "^8.11.3",
|
|
246
247
|
"pg-connection-string": "^2.3.0",
|
|
247
248
|
"pg-format": "^1.0.4",
|
|
248
249
|
"raw-body": "^2.4.1",
|
|
249
250
|
"selfsigned": "^2.0.0",
|
|
250
|
-
"stoppable": "^1.1.0",
|
|
251
251
|
"tar": "^6.1.12",
|
|
252
252
|
"triple-beam": "^1.4.1",
|
|
253
253
|
"uuid": "^11.0.0",
|
|
@@ -267,9 +267,9 @@
|
|
|
267
267
|
},
|
|
268
268
|
"devDependencies": {
|
|
269
269
|
"@aws-sdk/util-stream-node": "^3.350.0",
|
|
270
|
-
"@backstage/backend-plugin-api": "1.1.0-next.
|
|
271
|
-
"@backstage/backend-test-utils": "1.2.0-next.
|
|
272
|
-
"@backstage/cli": "0.29.3-next.
|
|
270
|
+
"@backstage/backend-plugin-api": "1.1.0-next.2",
|
|
271
|
+
"@backstage/backend-test-utils": "1.2.0-next.2",
|
|
272
|
+
"@backstage/cli": "0.29.3-next.2",
|
|
273
273
|
"@google-cloud/cloud-sql-connector": "^1.4.0",
|
|
274
274
|
"@types/archiver": "^6.0.0",
|
|
275
275
|
"@types/base64-stream": "^1.0.2",
|