@backstage/backend-defaults 0.6.0-next.0 → 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 +58 -0
- package/config.d.ts +74 -1
- 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/database/connectors/postgres.cjs.js +40 -6
- package/dist/entrypoints/database/connectors/postgres.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/rootLogger/WinstonLogger.cjs.js +9 -3
- package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js +15 -0
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js +5 -11
- package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js +5 -14
- package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +16 -14
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js +5 -14
- package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js +2 -10
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js +4 -9
- package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +7 -16
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js +16 -2
- package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js +23 -4
- package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/util.cjs.js +29 -1
- package/dist/entrypoints/urlReader/lib/util.cjs.js.map +1 -1
- package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js +1 -6
- package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +16 -3
- package/dist/package.json.cjs.js.map +1 -1
- package/dist/rootHttpRouter.d.ts +3 -1
- package/dist/urlReader.d.ts +17 -2
- package/package.json +26 -17
|
@@ -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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"}
|
|
@@ -79,9 +79,15 @@ class WinstonLogger {
|
|
|
79
79
|
const prefix = plugin || service;
|
|
80
80
|
const timestampColor = colorizer.colorize("timestamp", timestamp);
|
|
81
81
|
const prefixColor = colorizer.colorize("prefix", prefix);
|
|
82
|
-
const extraFields = Object.entries(fields).map(
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const extraFields = Object.entries(fields).map(([key, value]) => {
|
|
83
|
+
let stringValue = "";
|
|
84
|
+
try {
|
|
85
|
+
stringValue = `${value}`;
|
|
86
|
+
} catch (e) {
|
|
87
|
+
stringValue = "[field value not castable to string]";
|
|
88
|
+
}
|
|
89
|
+
return `${colorizer.colorize("field", `${key}`)}=${stringValue}`;
|
|
90
|
+
}).join(" ");
|
|
85
91
|
return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`;
|
|
86
92
|
})
|
|
87
93
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WinstonLogger.cjs.js","sources":["../../../src/entrypoints/rootLogger/WinstonLogger.ts"],"sourcesContent":["/*\n * Copyright 2023 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 LoggerService,\n RootLoggerService,\n} from '@backstage/backend-plugin-api';\nimport { JsonObject } from '@backstage/types';\nimport { Format, TransformableInfo } from 'logform';\nimport {\n Logger,\n format,\n createLogger,\n transports,\n transport as Transport,\n} from 'winston';\nimport { MESSAGE } from 'triple-beam';\nimport { escapeRegExp } from '../../lib/escapeRegExp';\n\n/**\n * @public\n */\nexport interface WinstonLoggerOptions {\n meta?: JsonObject;\n level?: string;\n format?: Format;\n transports?: Transport[];\n}\n\n/**\n * A {@link @backstage/backend-plugin-api#LoggerService} implementation based on winston.\n *\n * @public\n */\nexport class WinstonLogger implements RootLoggerService {\n #winston: Logger;\n #addRedactions?: (redactions: Iterable<string>) => void;\n\n /**\n * Creates a {@link WinstonLogger} instance.\n */\n static create(options: WinstonLoggerOptions): WinstonLogger {\n const redacter = WinstonLogger.redacter();\n const defaultFormatter =\n process.env.NODE_ENV === 'production'\n ? format.json()\n : WinstonLogger.colorFormat();\n\n let logger = createLogger({\n level: process.env.LOG_LEVEL || options.level || 'info',\n format: format.combine(\n options.format ?? defaultFormatter,\n redacter.format,\n ),\n transports: options.transports ?? new transports.Console(),\n });\n\n if (options.meta) {\n logger = logger.child(options.meta);\n }\n\n return new WinstonLogger(logger, redacter.add);\n }\n\n /**\n * Creates a winston log formatter for redacting secrets.\n */\n static redacter(): {\n format: Format;\n add: (redactions: Iterable<string>) => void;\n } {\n const redactionSet = new Set<string>();\n\n let redactionPattern: RegExp | undefined = undefined;\n\n return {\n format: format((obj: TransformableInfo) => {\n if (!redactionPattern || !obj) {\n return obj;\n }\n\n obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '***');\n\n return obj;\n })(),\n add(newRedactions) {\n let added = 0;\n for (const redactionToTrim of newRedactions) {\n // Trimming the string ensures that we don't accdentally get extra\n // newlines or other whitespace interfering with the redaction; this\n // can happen for example when using string literals in yaml\n const redaction = redactionToTrim.trim();\n // Exclude secrets that are empty or just one character in length. These\n // typically mean that you are running local dev or tests, or using the\n // --lax flag which sets things to just 'x'.\n if (redaction.length <= 1) {\n continue;\n }\n if (!redactionSet.has(redaction)) {\n redactionSet.add(redaction);\n added += 1;\n }\n }\n if (added > 0) {\n const redactions = Array.from(redactionSet)\n .map(r => escapeRegExp(r))\n .join('|');\n redactionPattern = new RegExp(`(${redactions})`, 'g');\n }\n },\n };\n }\n\n /**\n * Creates a pretty printed winston log formatter.\n */\n static colorFormat(): Format {\n const colorizer = format.colorize();\n\n return format.combine(\n format.timestamp(),\n format.colorize({\n colors: {\n timestamp: 'dim',\n prefix: 'blue',\n field: 'cyan',\n debug: 'grey',\n },\n }),\n format.printf((info: TransformableInfo) => {\n const { timestamp, level, message, plugin, service, ...fields } = info;\n const prefix = plugin || service;\n const timestampColor = colorizer.colorize('timestamp', timestamp);\n const prefixColor = colorizer.colorize('prefix', prefix);\n\n const extraFields = Object.entries(fields)\n .map(
|
|
1
|
+
{"version":3,"file":"WinstonLogger.cjs.js","sources":["../../../src/entrypoints/rootLogger/WinstonLogger.ts"],"sourcesContent":["/*\n * Copyright 2023 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 LoggerService,\n RootLoggerService,\n} from '@backstage/backend-plugin-api';\nimport { JsonObject } from '@backstage/types';\nimport { Format, TransformableInfo } from 'logform';\nimport {\n Logger,\n format,\n createLogger,\n transports,\n transport as Transport,\n} from 'winston';\nimport { MESSAGE } from 'triple-beam';\nimport { escapeRegExp } from '../../lib/escapeRegExp';\n\n/**\n * @public\n */\nexport interface WinstonLoggerOptions {\n meta?: JsonObject;\n level?: string;\n format?: Format;\n transports?: Transport[];\n}\n\n/**\n * A {@link @backstage/backend-plugin-api#LoggerService} implementation based on winston.\n *\n * @public\n */\nexport class WinstonLogger implements RootLoggerService {\n #winston: Logger;\n #addRedactions?: (redactions: Iterable<string>) => void;\n\n /**\n * Creates a {@link WinstonLogger} instance.\n */\n static create(options: WinstonLoggerOptions): WinstonLogger {\n const redacter = WinstonLogger.redacter();\n const defaultFormatter =\n process.env.NODE_ENV === 'production'\n ? format.json()\n : WinstonLogger.colorFormat();\n\n let logger = createLogger({\n level: process.env.LOG_LEVEL || options.level || 'info',\n format: format.combine(\n options.format ?? defaultFormatter,\n redacter.format,\n ),\n transports: options.transports ?? new transports.Console(),\n });\n\n if (options.meta) {\n logger = logger.child(options.meta);\n }\n\n return new WinstonLogger(logger, redacter.add);\n }\n\n /**\n * Creates a winston log formatter for redacting secrets.\n */\n static redacter(): {\n format: Format;\n add: (redactions: Iterable<string>) => void;\n } {\n const redactionSet = new Set<string>();\n\n let redactionPattern: RegExp | undefined = undefined;\n\n return {\n format: format((obj: TransformableInfo) => {\n if (!redactionPattern || !obj) {\n return obj;\n }\n\n obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '***');\n\n return obj;\n })(),\n add(newRedactions) {\n let added = 0;\n for (const redactionToTrim of newRedactions) {\n // Trimming the string ensures that we don't accdentally get extra\n // newlines or other whitespace interfering with the redaction; this\n // can happen for example when using string literals in yaml\n const redaction = redactionToTrim.trim();\n // Exclude secrets that are empty or just one character in length. These\n // typically mean that you are running local dev or tests, or using the\n // --lax flag which sets things to just 'x'.\n if (redaction.length <= 1) {\n continue;\n }\n if (!redactionSet.has(redaction)) {\n redactionSet.add(redaction);\n added += 1;\n }\n }\n if (added > 0) {\n const redactions = Array.from(redactionSet)\n .map(r => escapeRegExp(r))\n .join('|');\n redactionPattern = new RegExp(`(${redactions})`, 'g');\n }\n },\n };\n }\n\n /**\n * Creates a pretty printed winston log formatter.\n */\n static colorFormat(): Format {\n const colorizer = format.colorize();\n\n return format.combine(\n format.timestamp(),\n format.colorize({\n colors: {\n timestamp: 'dim',\n prefix: 'blue',\n field: 'cyan',\n debug: 'grey',\n },\n }),\n format.printf((info: TransformableInfo) => {\n const { timestamp, level, message, plugin, service, ...fields } = info;\n const prefix = plugin || service;\n const timestampColor = colorizer.colorize('timestamp', timestamp);\n const prefixColor = colorizer.colorize('prefix', prefix);\n\n const extraFields = Object.entries(fields)\n .map(([key, value]) => {\n let stringValue = '';\n\n try {\n stringValue = `${value}`;\n } catch (e) {\n stringValue = '[field value not castable to string]';\n }\n\n return `${colorizer.colorize('field', `${key}`)}=${stringValue}`;\n })\n .join(' ');\n\n return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`;\n }),\n );\n }\n\n private constructor(\n winston: Logger,\n addRedactions?: (redactions: Iterable<string>) => void,\n ) {\n this.#winston = winston;\n this.#addRedactions = addRedactions;\n }\n\n error(message: string, meta?: JsonObject): void {\n this.#winston.error(message, meta);\n }\n\n warn(message: string, meta?: JsonObject): void {\n this.#winston.warn(message, meta);\n }\n\n info(message: string, meta?: JsonObject): void {\n this.#winston.info(message, meta);\n }\n\n debug(message: string, meta?: JsonObject): void {\n this.#winston.debug(message, meta);\n }\n\n child(meta: JsonObject): LoggerService {\n return new WinstonLogger(this.#winston.child(meta));\n }\n\n addRedactions(redactions: Iterable<string>) {\n this.#addRedactions?.(redactions);\n }\n}\n"],"names":["format","createLogger","transports","MESSAGE","escapeRegExp"],"mappings":";;;;;;AA+CO,MAAM,aAA2C,CAAA;AAAA,EACtD,QAAA;AAAA,EACA,cAAA;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,OAAO,OAA8C,EAAA;AAC1D,IAAM,MAAA,QAAA,GAAW,cAAc,QAAS,EAAA;AACxC,IAAM,MAAA,gBAAA,GACJ,QAAQ,GAAI,CAAA,QAAA,KAAa,eACrBA,cAAO,CAAA,IAAA,EACP,GAAA,aAAA,CAAc,WAAY,EAAA;AAEhC,IAAA,IAAI,SAASC,oBAAa,CAAA;AAAA,MACxB,KAAO,EAAA,OAAA,CAAQ,GAAI,CAAA,SAAA,IAAa,QAAQ,KAAS,IAAA,MAAA;AAAA,MACjD,QAAQD,cAAO,CAAA,OAAA;AAAA,QACb,QAAQ,MAAU,IAAA,gBAAA;AAAA,QAClB,QAAS,CAAA;AAAA,OACX;AAAA,MACA,UAAY,EAAA,OAAA,CAAQ,UAAc,IAAA,IAAIE,mBAAW,OAAQ;AAAA,KAC1D,CAAA;AAED,IAAA,IAAI,QAAQ,IAAM,EAAA;AAChB,MAAS,MAAA,GAAA,MAAA,CAAO,KAAM,CAAA,OAAA,CAAQ,IAAI,CAAA;AAAA;AAGpC,IAAA,OAAO,IAAI,aAAA,CAAc,MAAQ,EAAA,QAAA,CAAS,GAAG,CAAA;AAAA;AAC/C;AAAA;AAAA;AAAA,EAKA,OAAO,QAGL,GAAA;AACA,IAAM,MAAA,YAAA,uBAAmB,GAAY,EAAA;AAErC,IAAA,IAAI,gBAAuC,GAAA,KAAA,CAAA;AAE3C,IAAO,OAAA;AAAA,MACL,MAAA,EAAQF,cAAO,CAAA,CAAC,GAA2B,KAAA;AACzC,QAAI,IAAA,CAAC,gBAAoB,IAAA,CAAC,GAAK,EAAA;AAC7B,UAAO,OAAA,GAAA;AAAA;AAGT,QAAA,GAAA,CAAIG,kBAAO,CAAI,GAAA,GAAA,CAAIA,kBAAO,CAAG,EAAA,OAAA,GAAU,kBAAkB,KAAK,CAAA;AAE9D,QAAO,OAAA,GAAA;AAAA,OACR,CAAE,EAAA;AAAA,MACH,IAAI,aAAe,EAAA;AACjB,QAAA,IAAI,KAAQ,GAAA,CAAA;AACZ,QAAA,KAAA,MAAW,mBAAmB,aAAe,EAAA;AAI3C,UAAM,MAAA,SAAA,GAAY,gBAAgB,IAAK,EAAA;AAIvC,UAAI,IAAA,SAAA,CAAU,UAAU,CAAG,EAAA;AACzB,YAAA;AAAA;AAEF,UAAA,IAAI,CAAC,YAAA,CAAa,GAAI,CAAA,SAAS,CAAG,EAAA;AAChC,YAAA,YAAA,CAAa,IAAI,SAAS,CAAA;AAC1B,YAAS,KAAA,IAAA,CAAA;AAAA;AACX;AAEF,QAAA,IAAI,QAAQ,CAAG,EAAA;AACb,UAAA,MAAM,UAAa,GAAA,KAAA,CAAM,IAAK,CAAA,YAAY,CACvC,CAAA,GAAA,CAAI,CAAK,CAAA,KAAAC,yBAAA,CAAa,CAAC,CAAC,CACxB,CAAA,IAAA,CAAK,GAAG,CAAA;AACX,UAAA,gBAAA,GAAmB,IAAI,MAAA,CAAO,CAAI,CAAA,EAAA,UAAU,KAAK,GAAG,CAAA;AAAA;AACtD;AACF,KACF;AAAA;AACF;AAAA;AAAA;AAAA,EAKA,OAAO,WAAsB,GAAA;AAC3B,IAAM,MAAA,SAAA,GAAYJ,eAAO,QAAS,EAAA;AAElC,IAAA,OAAOA,cAAO,CAAA,OAAA;AAAA,MACZA,eAAO,SAAU,EAAA;AAAA,MACjBA,eAAO,QAAS,CAAA;AAAA,QACd,MAAQ,EAAA;AAAA,UACN,SAAW,EAAA,KAAA;AAAA,UACX,MAAQ,EAAA,MAAA;AAAA,UACR,KAAO,EAAA,MAAA;AAAA,UACP,KAAO,EAAA;AAAA;AACT,OACD,CAAA;AAAA,MACDA,cAAA,CAAO,MAAO,CAAA,CAAC,IAA4B,KAAA;AACzC,QAAM,MAAA,EAAE,WAAW,KAAO,EAAA,OAAA,EAAS,QAAQ,OAAS,EAAA,GAAG,QAAW,GAAA,IAAA;AAClE,QAAA,MAAM,SAAS,MAAU,IAAA,OAAA;AACzB,QAAA,MAAM,cAAiB,GAAA,SAAA,CAAU,QAAS,CAAA,WAAA,EAAa,SAAS,CAAA;AAChE,QAAA,MAAM,WAAc,GAAA,SAAA,CAAU,QAAS,CAAA,QAAA,EAAU,MAAM,CAAA;AAEvD,QAAM,MAAA,WAAA,GAAc,MAAO,CAAA,OAAA,CAAQ,MAAM,CAAA,CACtC,IAAI,CAAC,CAAC,GAAK,EAAA,KAAK,CAAM,KAAA;AACrB,UAAA,IAAI,WAAc,GAAA,EAAA;AAElB,UAAI,IAAA;AACF,YAAA,WAAA,GAAc,GAAG,KAAK,CAAA,CAAA;AAAA,mBACf,CAAG,EAAA;AACV,YAAc,WAAA,GAAA,sCAAA;AAAA;AAGhB,UAAO,OAAA,CAAA,EAAG,UAAU,QAAS,CAAA,OAAA,EAAS,GAAG,GAAG,CAAA,CAAE,CAAC,CAAA,CAAA,EAAI,WAAW,CAAA,CAAA;AAAA,SAC/D,CACA,CAAA,IAAA,CAAK,GAAG,CAAA;AAEX,QAAO,OAAA,CAAA,EAAG,cAAc,CAAI,CAAA,EAAA,WAAW,IAAI,KAAK,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,EAAI,WAAW,CAAA,CAAA;AAAA,OAC3E;AAAA,KACH;AAAA;AACF,EAEQ,WAAA,CACN,SACA,aACA,EAAA;AACA,IAAA,IAAA,CAAK,QAAW,GAAA,OAAA;AAChB,IAAA,IAAA,CAAK,cAAiB,GAAA,aAAA;AAAA;AACxB,EAEA,KAAA,CAAM,SAAiB,IAAyB,EAAA;AAC9C,IAAK,IAAA,CAAA,QAAA,CAAS,KAAM,CAAA,OAAA,EAAS,IAAI,CAAA;AAAA;AACnC,EAEA,IAAA,CAAK,SAAiB,IAAyB,EAAA;AAC7C,IAAK,IAAA,CAAA,QAAA,CAAS,IAAK,CAAA,OAAA,EAAS,IAAI,CAAA;AAAA;AAClC,EAEA,IAAA,CAAK,SAAiB,IAAyB,EAAA;AAC7C,IAAK,IAAA,CAAA,QAAA,CAAS,IAAK,CAAA,OAAA,EAAS,IAAI,CAAA;AAAA;AAClC,EAEA,KAAA,CAAM,SAAiB,IAAyB,EAAA;AAC9C,IAAK,IAAA,CAAA,QAAA,CAAS,KAAM,CAAA,OAAA,EAAS,IAAI,CAAA;AAAA;AACnC,EAEA,MAAM,IAAiC,EAAA;AACrC,IAAA,OAAO,IAAI,aAAc,CAAA,IAAA,CAAK,QAAS,CAAA,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA;AACpD,EAEA,cAAc,UAA8B,EAAA;AAC1C,IAAA,IAAA,CAAK,iBAAiB,UAAU,CAAA;AAAA;AAEpC;;;;"}
|
|
@@ -19,6 +19,17 @@ class PluginTaskSchedulerImpl {
|
|
|
19
19
|
description: "Histogram of task run durations",
|
|
20
20
|
unit: "seconds"
|
|
21
21
|
});
|
|
22
|
+
this.lastStarted = meter.createGauge("backend_tasks.task.runs.started", {
|
|
23
|
+
description: "Epoch timestamp seconds when the task was last started",
|
|
24
|
+
unit: "seconds"
|
|
25
|
+
});
|
|
26
|
+
this.lastCompleted = meter.createGauge(
|
|
27
|
+
"backend_tasks.task.runs.completed",
|
|
28
|
+
{
|
|
29
|
+
description: "Epoch timestamp seconds when the task was last completed",
|
|
30
|
+
unit: "seconds"
|
|
31
|
+
}
|
|
32
|
+
);
|
|
22
33
|
this.shutdownInitiated = new Promise((shutdownInitiated) => {
|
|
23
34
|
rootLifecycle?.addShutdownHook(() => shutdownInitiated(true));
|
|
24
35
|
});
|
|
@@ -28,6 +39,8 @@ class PluginTaskSchedulerImpl {
|
|
|
28
39
|
shutdownInitiated;
|
|
29
40
|
counter;
|
|
30
41
|
duration;
|
|
42
|
+
lastStarted;
|
|
43
|
+
lastCompleted;
|
|
31
44
|
async triggerTask(id) {
|
|
32
45
|
const localTask = this.localTasksById.get(id);
|
|
33
46
|
if (localTask) {
|
|
@@ -89,6 +102,7 @@ class PluginTaskSchedulerImpl {
|
|
|
89
102
|
scope
|
|
90
103
|
};
|
|
91
104
|
this.counter.add(1, { ...labels, result: "started" });
|
|
105
|
+
this.lastStarted.record(Date.now() / 1e3, { taskId: task.id });
|
|
92
106
|
const startTime = process.hrtime();
|
|
93
107
|
try {
|
|
94
108
|
await tracer.startActiveSpan(`task ${task.id}`, async (span) => {
|
|
@@ -113,6 +127,7 @@ class PluginTaskSchedulerImpl {
|
|
|
113
127
|
const endTime = delta[0] + delta[1] / 1e9;
|
|
114
128
|
this.counter.add(1, labels);
|
|
115
129
|
this.duration.record(endTime, labels);
|
|
130
|
+
this.lastCompleted.record(Date.now() / 1e3, labels);
|
|
116
131
|
}
|
|
117
132
|
};
|
|
118
133
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginTaskSchedulerImpl.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n LoggerService,\n RootLifecycleService,\n SchedulerService,\n SchedulerServiceTaskDescriptor,\n SchedulerServiceTaskFunction,\n SchedulerServiceTaskInvocationDefinition,\n SchedulerServiceTaskRunner,\n SchedulerServiceTaskScheduleDefinition,\n} from '@backstage/backend-plugin-api';\nimport { Counter, Histogram, metrics, trace } from '@opentelemetry/api';\nimport { Knex } from 'knex';\nimport { Duration } from 'luxon';\nimport { LocalTaskWorker } from './LocalTaskWorker';\nimport { TaskWorker } from './TaskWorker';\nimport { TaskSettingsV2 } from './types';\nimport { delegateAbortController, TRACER_ID, validateId } from './util';\n\nconst tracer = trace.getTracer(TRACER_ID);\n\n/**\n * Implements the actual task management.\n */\nexport class PluginTaskSchedulerImpl implements SchedulerService {\n private readonly localTasksById = new Map<string, LocalTaskWorker>();\n private readonly allScheduledTasks: SchedulerServiceTaskDescriptor[] = [];\n private readonly shutdownInitiated: Promise<boolean>;\n\n private readonly counter: Counter;\n private readonly duration: Histogram;\n\n constructor(\n private readonly databaseFactory: () => Promise<Knex>,\n private readonly logger: LoggerService,\n rootLifecycle?: RootLifecycleService,\n ) {\n const meter = metrics.getMeter('default');\n this.counter = meter.createCounter('backend_tasks.task.runs.count', {\n description: 'Total number of times a task has been run',\n });\n this.duration = meter.createHistogram('backend_tasks.task.runs.duration', {\n description: 'Histogram of task run durations',\n unit: 'seconds',\n });\n this.shutdownInitiated = new Promise(shutdownInitiated => {\n rootLifecycle?.addShutdownHook(() => shutdownInitiated(true));\n });\n }\n\n async triggerTask(id: string): Promise<void> {\n const localTask = this.localTasksById.get(id);\n if (localTask) {\n localTask.trigger();\n return;\n }\n\n const knex = await this.databaseFactory();\n await TaskWorker.trigger(knex, id);\n }\n\n async scheduleTask(\n task: SchedulerServiceTaskScheduleDefinition &\n SchedulerServiceTaskInvocationDefinition,\n ): Promise<void> {\n validateId(task.id);\n const scope = task.scope ?? 'global';\n\n const settings: TaskSettingsV2 = {\n version: 2,\n cadence: parseDuration(task.frequency),\n initialDelayDuration:\n task.initialDelay && parseDuration(task.initialDelay),\n timeoutAfterDuration: parseDuration(task.timeout),\n };\n\n // Delegated abort controller that will abort either when the provided\n // controller aborts, or when a root lifecycle shutdown happens\n const abortController = delegateAbortController(task.signal);\n this.shutdownInitiated.then(() => abortController.abort());\n\n if (scope === 'global') {\n const knex = await this.databaseFactory();\n const worker = new TaskWorker(\n task.id,\n this.instrumentedFunction(task, scope),\n knex,\n this.logger.child({ task: task.id }),\n );\n await worker.start(settings, { signal: abortController.signal });\n } else {\n const worker = new LocalTaskWorker(\n task.id,\n this.instrumentedFunction(task, scope),\n this.logger.child({ task: task.id }),\n );\n worker.start(settings, { signal: abortController.signal });\n this.localTasksById.set(task.id, worker);\n }\n\n this.allScheduledTasks.push({\n id: task.id,\n scope: scope,\n settings: settings,\n });\n }\n\n createScheduledTaskRunner(\n schedule: SchedulerServiceTaskScheduleDefinition,\n ): SchedulerServiceTaskRunner {\n return {\n run: async task => {\n await this.scheduleTask({ ...task, ...schedule });\n },\n };\n }\n\n async getScheduledTasks(): Promise<SchedulerServiceTaskDescriptor[]> {\n return this.allScheduledTasks;\n }\n\n private instrumentedFunction(\n task: SchedulerServiceTaskInvocationDefinition,\n scope: string,\n ): SchedulerServiceTaskFunction {\n return async abort => {\n const labels: Record<string, string> = {\n taskId: task.id,\n scope,\n };\n this.counter.add(1, { ...labels, result: 'started' });\n\n const startTime = process.hrtime();\n\n try {\n await tracer.startActiveSpan(`task ${task.id}`, async span => {\n try {\n span.setAttributes(labels);\n await task.fn(abort);\n } catch (error) {\n if (error instanceof Error) {\n span.recordException(error);\n }\n throw error;\n } finally {\n span.end();\n }\n });\n labels.result = 'completed';\n } catch (ex) {\n labels.result = 'failed';\n throw ex;\n } finally {\n const delta = process.hrtime(startTime);\n const endTime = delta[0] + delta[1] / 1e9;\n this.counter.add(1, labels);\n this.duration.record(endTime, labels);\n }\n };\n }\n}\n\nexport function parseDuration(\n frequency: SchedulerServiceTaskScheduleDefinition['frequency'],\n): string {\n if (typeof frequency === 'object' && 'cron' in frequency) {\n return frequency.cron;\n }\n if (typeof frequency === 'object' && 'trigger' in frequency) {\n return frequency.trigger;\n }\n\n const parsed = Duration.isDuration(frequency)\n ? frequency\n : Duration.fromObject(frequency);\n\n if (!parsed.isValid) {\n throw new Error(\n `Invalid duration, ${parsed.invalidReason}: ${parsed.invalidExplanation}`,\n );\n }\n\n return parsed.toISO()!;\n}\n"],"names":["trace","TRACER_ID","metrics","TaskWorker","validateId","delegateAbortController","LocalTaskWorker","Duration"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginTaskSchedulerImpl.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n LoggerService,\n RootLifecycleService,\n SchedulerService,\n SchedulerServiceTaskDescriptor,\n SchedulerServiceTaskFunction,\n SchedulerServiceTaskInvocationDefinition,\n SchedulerServiceTaskRunner,\n SchedulerServiceTaskScheduleDefinition,\n} from '@backstage/backend-plugin-api';\nimport { Counter, Histogram, Gauge, metrics, trace } from '@opentelemetry/api';\nimport { Knex } from 'knex';\nimport { Duration } from 'luxon';\nimport { LocalTaskWorker } from './LocalTaskWorker';\nimport { TaskWorker } from './TaskWorker';\nimport { TaskSettingsV2 } from './types';\nimport { delegateAbortController, TRACER_ID, validateId } from './util';\n\nconst tracer = trace.getTracer(TRACER_ID);\n\n/**\n * Implements the actual task management.\n */\nexport class PluginTaskSchedulerImpl implements SchedulerService {\n private readonly localTasksById = new Map<string, LocalTaskWorker>();\n private readonly allScheduledTasks: SchedulerServiceTaskDescriptor[] = [];\n private readonly shutdownInitiated: Promise<boolean>;\n\n private readonly counter: Counter;\n private readonly duration: Histogram;\n private readonly lastStarted: Gauge;\n private readonly lastCompleted: Gauge;\n\n constructor(\n private readonly databaseFactory: () => Promise<Knex>,\n private readonly logger: LoggerService,\n rootLifecycle?: RootLifecycleService,\n ) {\n const meter = metrics.getMeter('default');\n this.counter = meter.createCounter('backend_tasks.task.runs.count', {\n description: 'Total number of times a task has been run',\n });\n this.duration = meter.createHistogram('backend_tasks.task.runs.duration', {\n description: 'Histogram of task run durations',\n unit: 'seconds',\n });\n this.lastStarted = meter.createGauge('backend_tasks.task.runs.started', {\n description: 'Epoch timestamp seconds when the task was last started',\n unit: 'seconds',\n });\n this.lastCompleted = meter.createGauge(\n 'backend_tasks.task.runs.completed',\n {\n description: 'Epoch timestamp seconds when the task was last completed',\n unit: 'seconds',\n },\n );\n this.shutdownInitiated = new Promise(shutdownInitiated => {\n rootLifecycle?.addShutdownHook(() => shutdownInitiated(true));\n });\n }\n\n async triggerTask(id: string): Promise<void> {\n const localTask = this.localTasksById.get(id);\n if (localTask) {\n localTask.trigger();\n return;\n }\n\n const knex = await this.databaseFactory();\n await TaskWorker.trigger(knex, id);\n }\n\n async scheduleTask(\n task: SchedulerServiceTaskScheduleDefinition &\n SchedulerServiceTaskInvocationDefinition,\n ): Promise<void> {\n validateId(task.id);\n const scope = task.scope ?? 'global';\n\n const settings: TaskSettingsV2 = {\n version: 2,\n cadence: parseDuration(task.frequency),\n initialDelayDuration:\n task.initialDelay && parseDuration(task.initialDelay),\n timeoutAfterDuration: parseDuration(task.timeout),\n };\n\n // Delegated abort controller that will abort either when the provided\n // controller aborts, or when a root lifecycle shutdown happens\n const abortController = delegateAbortController(task.signal);\n this.shutdownInitiated.then(() => abortController.abort());\n\n if (scope === 'global') {\n const knex = await this.databaseFactory();\n const worker = new TaskWorker(\n task.id,\n this.instrumentedFunction(task, scope),\n knex,\n this.logger.child({ task: task.id }),\n );\n await worker.start(settings, { signal: abortController.signal });\n } else {\n const worker = new LocalTaskWorker(\n task.id,\n this.instrumentedFunction(task, scope),\n this.logger.child({ task: task.id }),\n );\n worker.start(settings, { signal: abortController.signal });\n this.localTasksById.set(task.id, worker);\n }\n\n this.allScheduledTasks.push({\n id: task.id,\n scope: scope,\n settings: settings,\n });\n }\n\n createScheduledTaskRunner(\n schedule: SchedulerServiceTaskScheduleDefinition,\n ): SchedulerServiceTaskRunner {\n return {\n run: async task => {\n await this.scheduleTask({ ...task, ...schedule });\n },\n };\n }\n\n async getScheduledTasks(): Promise<SchedulerServiceTaskDescriptor[]> {\n return this.allScheduledTasks;\n }\n\n private instrumentedFunction(\n task: SchedulerServiceTaskInvocationDefinition,\n scope: string,\n ): SchedulerServiceTaskFunction {\n return async abort => {\n const labels: Record<string, string> = {\n taskId: task.id,\n scope,\n };\n this.counter.add(1, { ...labels, result: 'started' });\n this.lastStarted.record(Date.now() / 1000, { taskId: task.id });\n\n const startTime = process.hrtime();\n\n try {\n await tracer.startActiveSpan(`task ${task.id}`, async span => {\n try {\n span.setAttributes(labels);\n await task.fn(abort);\n } catch (error) {\n if (error instanceof Error) {\n span.recordException(error);\n }\n throw error;\n } finally {\n span.end();\n }\n });\n labels.result = 'completed';\n } catch (ex) {\n labels.result = 'failed';\n throw ex;\n } finally {\n const delta = process.hrtime(startTime);\n const endTime = delta[0] + delta[1] / 1e9;\n this.counter.add(1, labels);\n this.duration.record(endTime, labels);\n this.lastCompleted.record(Date.now() / 1000, labels);\n }\n };\n }\n}\n\nexport function parseDuration(\n frequency: SchedulerServiceTaskScheduleDefinition['frequency'],\n): string {\n if (typeof frequency === 'object' && 'cron' in frequency) {\n return frequency.cron;\n }\n if (typeof frequency === 'object' && 'trigger' in frequency) {\n return frequency.trigger;\n }\n\n const parsed = Duration.isDuration(frequency)\n ? frequency\n : Duration.fromObject(frequency);\n\n if (!parsed.isValid) {\n throw new Error(\n `Invalid duration, ${parsed.invalidReason}: ${parsed.invalidExplanation}`,\n );\n }\n\n return parsed.toISO()!;\n}\n"],"names":["trace","TRACER_ID","metrics","TaskWorker","validateId","delegateAbortController","LocalTaskWorker","Duration"],"mappings":"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"}
|