@backstage/backend-defaults 0.15.3-next.0 → 0.16.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 CHANGED
@@ -1,5 +1,44 @@
1
1
  # @backstage/backend-defaults
2
2
 
3
+ ## 0.16.0-next.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available.
8
+ - 5fcbef2: Updated dependency `express-rate-limit` to `^8.0.0`.
9
+ - Updated dependencies
10
+ - @backstage/backend-plugin-api@1.8.0-next.1
11
+ - @backstage/integration@2.0.0-next.2
12
+ - @backstage/backend-app-api@1.6.0-next.1
13
+ - @backstage/plugin-auth-node@0.6.14-next.2
14
+ - @backstage/plugin-events-node@0.4.20-next.1
15
+ - @backstage/plugin-permission-node@0.10.11-next.1
16
+
17
+ ## 0.16.0-next.1
18
+
19
+ ### Minor Changes
20
+
21
+ - 0e7d8f9: The scheduler service now uses the metrics service to create metrics, providing plugin-scoped attribution.
22
+ - 527cf88: **BREAKING** Removed deprecated `BitbucketUrlReader`. Use the `BitbucketCloudUrlReader` or the `BitbucketServerUrlReader` instead.
23
+
24
+ ### Patch Changes
25
+
26
+ - 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
27
+ - Updated dependencies
28
+ - @backstage/cli-node@0.2.19-next.1
29
+ - @backstage/integration@2.0.0-next.1
30
+ - @backstage/plugin-auth-node@0.6.14-next.1
31
+ - @backstage/backend-app-api@1.5.1-next.0
32
+ - @backstage/backend-dev-utils@0.1.7
33
+ - @backstage/backend-plugin-api@1.7.1-next.0
34
+ - @backstage/config@1.3.6
35
+ - @backstage/config-loader@1.10.9-next.0
36
+ - @backstage/errors@1.2.7
37
+ - @backstage/integration-aws-node@0.1.20
38
+ - @backstage/types@1.2.2
39
+ - @backstage/plugin-events-node@0.4.20-next.0
40
+ - @backstage/plugin-permission-node@0.10.11-next.0
41
+
3
42
  ## 0.15.3-next.0
4
43
 
5
44
  ### Patch Changes
@@ -79,26 +79,19 @@ class DefaultActionsRegistryService {
79
79
  input.error
80
80
  );
81
81
  }
82
- try {
83
- const result = await action.action({
84
- input: input.data,
85
- credentials,
86
- logger: this.logger
87
- });
88
- const output = action.schema?.output ? action.schema.output(zod.z).safeParse(result?.output) : { success: true, data: result?.output };
89
- if (!output.success) {
90
- throw new errors.InputError(
91
- `Invalid output from action "${req.params.actionId}"`,
92
- output.error
93
- );
94
- }
95
- res.json({ output: output.data });
96
- } catch (error) {
97
- throw new errors.ForwardedError(
98
- `Failed execution of action "${req.params.actionId}"`,
99
- error
82
+ const result = await action.action({
83
+ input: input.data,
84
+ credentials,
85
+ logger: this.logger
86
+ });
87
+ const output = action.schema?.output ? action.schema.output(zod.z).safeParse(result?.output) : { success: true, data: result?.output };
88
+ if (!output.success) {
89
+ throw new errors.InputError(
90
+ `Invalid output from action "${req.params.actionId}"`,
91
+ output.error
100
92
  );
101
93
  }
94
+ res.json({ output: output.data });
102
95
  }
103
96
  );
104
97
  return router;
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultActionsRegistryService.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n HttpAuthService,\n LoggerService,\n PluginMetadataService,\n} from '@backstage/backend-plugin-api';\nimport PromiseRouter from 'express-promise-router';\nimport { Router, json } from 'express';\nimport { z, AnyZodObject } from 'zod';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport {\n ActionsRegistryActionOptions,\n ActionsRegistryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport {\n ForwardedError,\n InputError,\n NotAllowedError,\n NotFoundError,\n} from '@backstage/errors';\n\nexport class DefaultActionsRegistryService implements ActionsRegistryService {\n private actions: Map<string, ActionsRegistryActionOptions<any, any>> =\n new Map();\n\n private readonly logger: LoggerService;\n private readonly httpAuth: HttpAuthService;\n private readonly auth: AuthService;\n private readonly metadata: PluginMetadataService;\n\n private constructor(\n logger: LoggerService,\n httpAuth: HttpAuthService,\n auth: AuthService,\n metadata: PluginMetadataService,\n ) {\n this.logger = logger;\n this.httpAuth = httpAuth;\n this.auth = auth;\n this.metadata = metadata;\n }\n\n static create({\n httpAuth,\n logger,\n auth,\n metadata,\n }: {\n httpAuth: HttpAuthService;\n logger: LoggerService;\n auth: AuthService;\n metadata: PluginMetadataService;\n }): DefaultActionsRegistryService {\n return new DefaultActionsRegistryService(logger, httpAuth, auth, metadata);\n }\n\n createRouter(): Router {\n const router = PromiseRouter();\n router.use(json());\n\n router.get('/.backstage/actions/v1/actions', (_, res) => {\n return res.json({\n actions: Array.from(this.actions.entries()).map(([id, action]) => ({\n id,\n ...action,\n attributes: {\n // Inspired by the @modelcontextprotocol/sdk defaults for the hints.\n // https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812\n destructive: action.attributes?.destructive ?? true,\n idempotent: action.attributes?.idempotent ?? false,\n readOnly: action.attributes?.readOnly ?? false,\n },\n schema: {\n input: action.schema?.input\n ? zodToJsonSchema(action.schema.input(z))\n : zodToJsonSchema(z.object({})),\n output: action.schema?.output\n ? zodToJsonSchema(action.schema.output(z))\n : zodToJsonSchema(z.object({})),\n },\n })),\n });\n });\n\n router.post(\n '/.backstage/actions/v1/actions/:actionId/invoke',\n async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n if (this.auth.isPrincipal(credentials, 'user')) {\n if (!credentials.principal.actor) {\n throw new NotAllowedError(\n `Actions must be invoked by a service, not a user`,\n );\n }\n } else if (this.auth.isPrincipal(credentials, 'none')) {\n throw new NotAllowedError(\n `Actions must be invoked by a service, not an anonymous request`,\n );\n }\n\n const action = this.actions.get(req.params.actionId);\n\n if (!action) {\n throw new NotFoundError(`Action \"${req.params.actionId}\" not found`);\n }\n\n const input = action.schema?.input\n ? action.schema.input(z).safeParse(req.body)\n : ({ success: true, data: undefined } as const);\n\n if (!input.success) {\n throw new InputError(\n `Invalid input to action \"${req.params.actionId}\"`,\n input.error,\n );\n }\n\n try {\n const result = await action.action({\n input: input.data,\n credentials,\n logger: this.logger,\n });\n\n const output = action.schema?.output\n ? action.schema.output(z).safeParse(result?.output)\n : ({ success: true, data: result?.output } as const);\n\n if (!output.success) {\n throw new InputError(\n `Invalid output from action \"${req.params.actionId}\"`,\n output.error,\n );\n }\n\n res.json({ output: output.data });\n } catch (error) {\n throw new ForwardedError(\n `Failed execution of action \"${req.params.actionId}\"`,\n error,\n );\n }\n },\n );\n return router;\n }\n\n register<\n TInputSchema extends AnyZodObject,\n TOutputSchema extends AnyZodObject,\n >(options: ActionsRegistryActionOptions<TInputSchema, TOutputSchema>): void {\n const id = `${this.metadata.getId()}:${options.name}`;\n\n if (this.actions.has(id)) {\n throw new Error(`Action with id \"${id}\" is already registered`);\n }\n\n this.actions.set(id, options);\n }\n}\n"],"names":["PromiseRouter","json","zodToJsonSchema","z","NotAllowedError","NotFoundError","InputError","ForwardedError"],"mappings":"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"}
1
+ {"version":3,"file":"DefaultActionsRegistryService.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n HttpAuthService,\n LoggerService,\n PluginMetadataService,\n} from '@backstage/backend-plugin-api';\nimport PromiseRouter from 'express-promise-router';\nimport { Router, json } from 'express';\nimport { z, AnyZodObject } from 'zod';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport {\n ActionsRegistryActionOptions,\n ActionsRegistryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { InputError, NotAllowedError, NotFoundError } from '@backstage/errors';\n\nexport class DefaultActionsRegistryService implements ActionsRegistryService {\n private actions: Map<string, ActionsRegistryActionOptions<any, any>> =\n new Map();\n\n private readonly logger: LoggerService;\n private readonly httpAuth: HttpAuthService;\n private readonly auth: AuthService;\n private readonly metadata: PluginMetadataService;\n\n private constructor(\n logger: LoggerService,\n httpAuth: HttpAuthService,\n auth: AuthService,\n metadata: PluginMetadataService,\n ) {\n this.logger = logger;\n this.httpAuth = httpAuth;\n this.auth = auth;\n this.metadata = metadata;\n }\n\n static create({\n httpAuth,\n logger,\n auth,\n metadata,\n }: {\n httpAuth: HttpAuthService;\n logger: LoggerService;\n auth: AuthService;\n metadata: PluginMetadataService;\n }): DefaultActionsRegistryService {\n return new DefaultActionsRegistryService(logger, httpAuth, auth, metadata);\n }\n\n createRouter(): Router {\n const router = PromiseRouter();\n router.use(json());\n\n router.get('/.backstage/actions/v1/actions', (_, res) => {\n return res.json({\n actions: Array.from(this.actions.entries()).map(([id, action]) => ({\n id,\n ...action,\n attributes: {\n // Inspired by the @modelcontextprotocol/sdk defaults for the hints.\n // https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812\n destructive: action.attributes?.destructive ?? true,\n idempotent: action.attributes?.idempotent ?? false,\n readOnly: action.attributes?.readOnly ?? false,\n },\n schema: {\n input: action.schema?.input\n ? zodToJsonSchema(action.schema.input(z))\n : zodToJsonSchema(z.object({})),\n output: action.schema?.output\n ? zodToJsonSchema(action.schema.output(z))\n : zodToJsonSchema(z.object({})),\n },\n })),\n });\n });\n\n router.post(\n '/.backstage/actions/v1/actions/:actionId/invoke',\n async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n if (this.auth.isPrincipal(credentials, 'user')) {\n if (!credentials.principal.actor) {\n throw new NotAllowedError(\n `Actions must be invoked by a service, not a user`,\n );\n }\n } else if (this.auth.isPrincipal(credentials, 'none')) {\n throw new NotAllowedError(\n `Actions must be invoked by a service, not an anonymous request`,\n );\n }\n\n const action = this.actions.get(req.params.actionId);\n\n if (!action) {\n throw new NotFoundError(`Action \"${req.params.actionId}\" not found`);\n }\n\n const input = action.schema?.input\n ? action.schema.input(z).safeParse(req.body)\n : ({ success: true, data: undefined } as const);\n\n if (!input.success) {\n throw new InputError(\n `Invalid input to action \"${req.params.actionId}\"`,\n input.error,\n );\n }\n\n const result = await action.action({\n input: input.data,\n credentials,\n logger: this.logger,\n });\n\n const output = action.schema?.output\n ? action.schema.output(z).safeParse(result?.output)\n : ({ success: true, data: result?.output } as const);\n\n if (!output.success) {\n throw new InputError(\n `Invalid output from action \"${req.params.actionId}\"`,\n output.error,\n );\n }\n\n res.json({ output: output.data });\n },\n );\n return router;\n }\n\n register<\n TInputSchema extends AnyZodObject,\n TOutputSchema extends AnyZodObject,\n >(options: ActionsRegistryActionOptions<TInputSchema, TOutputSchema>): void {\n const id = `${this.metadata.getId()}:${options.name}`;\n\n if (this.actions.has(id)) {\n throw new Error(`Action with id \"${id}\" is already registered`);\n }\n\n this.actions.set(id, options);\n }\n}\n"],"names":["PromiseRouter","json","zodToJsonSchema","z","NotAllowedError","NotFoundError","InputError"],"mappings":"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"}
@@ -29,6 +29,7 @@ class DefaultSchedulerService {
29
29
  options.pluginMetadata.getId(),
30
30
  databaseFactory,
31
31
  options.logger,
32
+ options.metrics,
32
33
  options.rootLifecycle
33
34
  );
34
35
  options.httpRouter.use(scheduler.getRouter());
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultSchedulerService.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/DefaultSchedulerService.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 DatabaseService,\n HttpRouterService,\n LoggerService,\n PluginMetadataService,\n RootLifecycleService,\n SchedulerService,\n} from '@backstage/backend-plugin-api';\nimport { once } from 'lodash';\nimport { Duration } from 'luxon';\nimport { migrateBackendTasks } from '../database/migrateBackendTasks';\nimport { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl';\nimport { PluginTaskSchedulerJanitor } from './PluginTaskSchedulerJanitor';\n\n/**\n * Default implementation of the task scheduler service.\n *\n * @public\n */\nexport class DefaultSchedulerService {\n static create(options: {\n database: DatabaseService;\n logger: LoggerService;\n rootLifecycle: RootLifecycleService;\n httpRouter: HttpRouterService;\n pluginMetadata: PluginMetadataService;\n }): SchedulerService {\n const databaseFactory = once(async () => {\n const knex = await options.database.getClient();\n\n if (!options.database.migrations?.skip) {\n await migrateBackendTasks(knex);\n }\n\n if (process.env.NODE_ENV !== 'test') {\n const abortController = new AbortController();\n const janitor = new PluginTaskSchedulerJanitor({\n knex,\n waitBetweenRuns: Duration.fromObject({ minutes: 1 }),\n logger: options.logger,\n });\n\n options.rootLifecycle.addShutdownHook(() => abortController.abort());\n janitor.start(abortController.signal);\n }\n\n return knex;\n });\n\n const scheduler = new PluginTaskSchedulerImpl(\n options.pluginMetadata.getId(),\n databaseFactory,\n options.logger,\n options.rootLifecycle,\n );\n\n options.httpRouter.use(scheduler.getRouter());\n\n return scheduler;\n }\n}\n"],"names":["once","migrateBackendTasks","PluginTaskSchedulerJanitor","Duration","PluginTaskSchedulerImpl"],"mappings":";;;;;;;;AAmCO,MAAM,uBAAA,CAAwB;AAAA,EACnC,OAAO,OAAO,OAAA,EAMO;AACnB,IAAA,MAAM,eAAA,GAAkBA,YAAK,YAAY;AACvC,MAAA,MAAM,IAAA,GAAO,MAAM,OAAA,CAAQ,QAAA,CAAS,SAAA,EAAU;AAE9C,MAAA,IAAI,CAAC,OAAA,CAAQ,QAAA,CAAS,UAAA,EAAY,IAAA,EAAM;AACtC,QAAA,MAAMC,wCAAoB,IAAI,CAAA;AAAA,MAChC;AAEA,MAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,MAAA,EAAQ;AACnC,QAAA,MAAM,eAAA,GAAkB,IAAI,eAAA,EAAgB;AAC5C,QAAA,MAAM,OAAA,GAAU,IAAIC,qDAAA,CAA2B;AAAA,UAC7C,IAAA;AAAA,UACA,iBAAiBC,cAAA,CAAS,UAAA,CAAW,EAAE,OAAA,EAAS,GAAG,CAAA;AAAA,UACnD,QAAQ,OAAA,CAAQ;AAAA,SACjB,CAAA;AAED,QAAA,OAAA,CAAQ,aAAA,CAAc,eAAA,CAAgB,MAAM,eAAA,CAAgB,OAAO,CAAA;AACnE,QAAA,OAAA,CAAQ,KAAA,CAAM,gBAAgB,MAAM,CAAA;AAAA,MACtC;AAEA,MAAA,OAAO,IAAA;AAAA,IACT,CAAC,CAAA;AAED,IAAA,MAAM,YAAY,IAAIC,+CAAA;AAAA,MACpB,OAAA,CAAQ,eAAe,KAAA,EAAM;AAAA,MAC7B,eAAA;AAAA,MACA,OAAA,CAAQ,MAAA;AAAA,MACR,OAAA,CAAQ;AAAA,KACV;AAEA,IAAA,OAAA,CAAQ,UAAA,CAAW,GAAA,CAAI,SAAA,CAAU,SAAA,EAAW,CAAA;AAE5C,IAAA,OAAO,SAAA;AAAA,EACT;AACF;;;;"}
1
+ {"version":3,"file":"DefaultSchedulerService.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/DefaultSchedulerService.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 DatabaseService,\n HttpRouterService,\n LoggerService,\n PluginMetadataService,\n RootLifecycleService,\n SchedulerService,\n} from '@backstage/backend-plugin-api';\nimport { once } from 'lodash';\nimport { Duration } from 'luxon';\nimport { migrateBackendTasks } from '../database/migrateBackendTasks';\nimport { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl';\nimport { PluginTaskSchedulerJanitor } from './PluginTaskSchedulerJanitor';\nimport { MetricsService } from '@backstage/backend-plugin-api/alpha';\n\n/**\n * Default implementation of the task scheduler service.\n *\n * @public\n */\nexport class DefaultSchedulerService {\n static create(options: {\n database: DatabaseService;\n logger: LoggerService;\n metrics: MetricsService;\n rootLifecycle: RootLifecycleService;\n httpRouter: HttpRouterService;\n pluginMetadata: PluginMetadataService;\n }): SchedulerService {\n const databaseFactory = once(async () => {\n const knex = await options.database.getClient();\n\n if (!options.database.migrations?.skip) {\n await migrateBackendTasks(knex);\n }\n\n if (process.env.NODE_ENV !== 'test') {\n const abortController = new AbortController();\n const janitor = new PluginTaskSchedulerJanitor({\n knex,\n waitBetweenRuns: Duration.fromObject({ minutes: 1 }),\n logger: options.logger,\n });\n\n options.rootLifecycle.addShutdownHook(() => abortController.abort());\n janitor.start(abortController.signal);\n }\n\n return knex;\n });\n\n const scheduler = new PluginTaskSchedulerImpl(\n options.pluginMetadata.getId(),\n databaseFactory,\n options.logger,\n options.metrics,\n options.rootLifecycle,\n );\n\n options.httpRouter.use(scheduler.getRouter());\n\n return scheduler;\n }\n}\n"],"names":["once","migrateBackendTasks","PluginTaskSchedulerJanitor","Duration","PluginTaskSchedulerImpl"],"mappings":";;;;;;;;AAoCO,MAAM,uBAAA,CAAwB;AAAA,EACnC,OAAO,OAAO,OAAA,EAOO;AACnB,IAAA,MAAM,eAAA,GAAkBA,YAAK,YAAY;AACvC,MAAA,MAAM,IAAA,GAAO,MAAM,OAAA,CAAQ,QAAA,CAAS,SAAA,EAAU;AAE9C,MAAA,IAAI,CAAC,OAAA,CAAQ,QAAA,CAAS,UAAA,EAAY,IAAA,EAAM;AACtC,QAAA,MAAMC,wCAAoB,IAAI,CAAA;AAAA,MAChC;AAEA,MAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,MAAA,EAAQ;AACnC,QAAA,MAAM,eAAA,GAAkB,IAAI,eAAA,EAAgB;AAC5C,QAAA,MAAM,OAAA,GAAU,IAAIC,qDAAA,CAA2B;AAAA,UAC7C,IAAA;AAAA,UACA,iBAAiBC,cAAA,CAAS,UAAA,CAAW,EAAE,OAAA,EAAS,GAAG,CAAA;AAAA,UACnD,QAAQ,OAAA,CAAQ;AAAA,SACjB,CAAA;AAED,QAAA,OAAA,CAAQ,aAAA,CAAc,eAAA,CAAgB,MAAM,eAAA,CAAgB,OAAO,CAAA;AACnE,QAAA,OAAA,CAAQ,KAAA,CAAM,gBAAgB,MAAM,CAAA;AAAA,MACtC;AAEA,MAAA,OAAO,IAAA;AAAA,IACT,CAAC,CAAA;AAED,IAAA,MAAM,YAAY,IAAIC,+CAAA;AAAA,MACpB,OAAA,CAAQ,eAAe,KAAA,EAAM;AAAA,MAC7B,eAAA;AAAA,MACA,OAAA,CAAQ,MAAA;AAAA,MACR,OAAA,CAAQ,OAAA;AAAA,MACR,OAAA,CAAQ;AAAA,KACV;AAEA,IAAA,OAAA,CAAQ,UAAA,CAAW,GAAA,CAAI,SAAA,CAAU,SAAA,EAAW,CAAA;AAE5C,IAAA,OAAO,SAAA;AAAA,EACT;AACF;;;;"}
@@ -7,6 +7,7 @@ var util = require('./util.cjs.js');
7
7
 
8
8
  class LocalTaskWorker {
9
9
  abortWait;
10
+ taskAbortController;
10
11
  #taskState = {
11
12
  status: "idle"
12
13
  };
@@ -59,6 +60,12 @@ class LocalTaskWorker {
59
60
  }
60
61
  this.abortWait.abort();
61
62
  }
63
+ cancel() {
64
+ if (!this.taskAbortController) {
65
+ throw new errors.ConflictError(`Task ${this.taskId} is not running`);
66
+ }
67
+ this.taskAbortController.abort();
68
+ }
62
69
  taskState() {
63
70
  return this.#taskState;
64
71
  }
@@ -87,10 +94,10 @@ class LocalTaskWorker {
87
94
  * Makes a single attempt at running the task to completion.
88
95
  */
89
96
  async runOnce(settings, signal) {
90
- const taskAbortController = util.delegateAbortController(signal);
97
+ this.taskAbortController = util.delegateAbortController(signal);
91
98
  const timeoutDuration = luxon.Duration.fromISO(settings.timeoutAfterDuration);
92
99
  const timeoutHandle = setTimeout(() => {
93
- taskAbortController.abort();
100
+ this.taskAbortController?.abort();
94
101
  }, timeoutDuration.as("milliseconds"));
95
102
  this.#taskState = {
96
103
  status: "running",
@@ -103,7 +110,7 @@ class LocalTaskWorker {
103
110
  status: "running"
104
111
  };
105
112
  try {
106
- await this.fn(taskAbortController.signal);
113
+ await this.fn(this.taskAbortController.signal);
107
114
  this.#taskState.lastRunEndedAt = luxon.DateTime.utc().toISO();
108
115
  this.#taskState.lastRunError = void 0;
109
116
  } catch (e) {
@@ -111,7 +118,8 @@ class LocalTaskWorker {
111
118
  this.#taskState.lastRunError = util.serializeError(e);
112
119
  }
113
120
  clearTimeout(timeoutHandle);
114
- taskAbortController.abort();
121
+ this.taskAbortController.abort();
122
+ this.taskAbortController = void 0;
115
123
  }
116
124
  /**
117
125
  * Sleeps until it's time to run the task again.
@@ -1 +1 @@
1
- {"version":3,"file":"LocalTaskWorker.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/LocalTaskWorker.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api';\nimport { ConflictError } from '@backstage/errors';\nimport { CronTime } from 'cron';\nimport { DateTime, Duration } from 'luxon';\nimport { TaskSettingsV2, TaskApiTasksResponse } from './types';\nimport { delegateAbortController, serializeError, sleep } from './util';\n\n/**\n * Implements tasks that run locally without cross-host collaboration.\n *\n * @private\n */\nexport class LocalTaskWorker {\n private abortWait: AbortController | undefined;\n #taskState: Exclude<TaskApiTasksResponse['taskState'], null> = {\n status: 'idle',\n };\n #workerState: TaskApiTasksResponse['workerState'] = {\n status: 'idle',\n };\n\n private readonly taskId: string;\n private readonly fn: SchedulerServiceTaskFunction;\n private readonly logger: LoggerService;\n\n constructor(\n taskId: string,\n fn: SchedulerServiceTaskFunction,\n logger: LoggerService,\n ) {\n this.taskId = taskId;\n this.fn = fn;\n this.logger = logger;\n }\n\n start(settings: TaskSettingsV2, options: { signal: AbortSignal }) {\n this.logger.info(\n `Registered scheduled task: ${this.taskId}, ${JSON.stringify(settings)}`,\n );\n\n (async () => {\n let attemptNum = 1;\n for (;;) {\n try {\n await this.performInitialWait(settings, options.signal);\n\n while (!options.signal.aborted) {\n const startTime = process.hrtime();\n await this.runOnce(settings, options.signal);\n const timeTaken = process.hrtime(startTime);\n await this.waitUntilNext(\n settings,\n (timeTaken[0] + timeTaken[1] / 1e9) * 1000,\n options.signal,\n );\n }\n\n this.logger.info(`Task worker finished: ${this.taskId}`);\n attemptNum = 0;\n break;\n } catch (e) {\n attemptNum += 1;\n this.logger.warn(\n `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`,\n );\n await sleep(Duration.fromObject({ seconds: 1 }));\n }\n }\n })();\n }\n\n trigger(): void {\n if (!this.abortWait) {\n throw new ConflictError(`Task ${this.taskId} is currently running`);\n }\n this.abortWait.abort();\n }\n\n taskState(): TaskApiTasksResponse['taskState'] {\n return this.#taskState;\n }\n\n workerState(): TaskApiTasksResponse['workerState'] {\n return this.#workerState;\n }\n\n /**\n * Does the once-at-startup initial wait, if configured.\n */\n private async performInitialWait(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n if (settings.initialDelayDuration) {\n const parsedDuration = Duration.fromISO(settings.initialDelayDuration);\n\n this.#taskState = {\n status: 'idle',\n startsAt: DateTime.utc().plus(parsedDuration).toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'initial-wait',\n };\n\n await this.sleep(parsedDuration, signal);\n }\n }\n\n /**\n * Makes a single attempt at running the task to completion.\n */\n private async runOnce(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n // Abort the task execution either if the worker is stopped, or if the\n // task timeout is hit\n const taskAbortController = delegateAbortController(signal);\n const timeoutDuration = Duration.fromISO(settings.timeoutAfterDuration);\n const timeoutHandle = setTimeout(() => {\n taskAbortController.abort();\n }, timeoutDuration.as('milliseconds'));\n\n this.#taskState = {\n status: 'running',\n startedAt: DateTime.utc().toISO()!,\n timesOutAt: DateTime.utc().plus(timeoutDuration).toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'running',\n };\n\n try {\n await this.fn(taskAbortController.signal);\n this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;\n this.#taskState.lastRunError = undefined;\n } catch (e) {\n this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;\n this.#taskState.lastRunError = serializeError(e);\n }\n\n // release resources\n clearTimeout(timeoutHandle);\n taskAbortController.abort();\n }\n\n /**\n * Sleeps until it's time to run the task again.\n */\n private async waitUntilNext(\n settings: TaskSettingsV2,\n lastRunMillis: number,\n signal: AbortSignal,\n ) {\n if (signal.aborted) {\n return;\n }\n\n const isCron = !settings.cadence.startsWith('P');\n let dt: number;\n\n if (isCron) {\n const nextRun = +new CronTime(settings.cadence).sendAt().toJSDate();\n dt = nextRun - Date.now();\n } else {\n dt =\n Duration.fromISO(settings.cadence).as('milliseconds') - lastRunMillis;\n }\n\n dt = Math.max(dt, 0);\n const startsAt = DateTime.now().plus(Duration.fromMillis(dt));\n\n this.#taskState = {\n status: 'idle',\n startsAt: startsAt.toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'idle',\n };\n\n this.logger.debug(\n `task: ${this.taskId} will next occur around ${startsAt}`,\n );\n\n await this.sleep(Duration.fromMillis(dt), signal);\n }\n\n private async sleep(\n duration: Duration,\n abortSignal: AbortSignal,\n ): Promise<void> {\n this.abortWait = delegateAbortController(abortSignal);\n await sleep(duration, this.abortWait.signal);\n this.abortWait.abort(); // cleans up resources\n this.abortWait = undefined;\n }\n}\n"],"names":["sleep","Duration","ConflictError","DateTime","delegateAbortController","serializeError","CronTime"],"mappings":"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"}
1
+ {"version":3,"file":"LocalTaskWorker.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/LocalTaskWorker.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api';\nimport { ConflictError } from '@backstage/errors';\nimport { CronTime } from 'cron';\nimport { DateTime, Duration } from 'luxon';\nimport { TaskSettingsV2, TaskApiTasksResponse } from './types';\nimport { delegateAbortController, serializeError, sleep } from './util';\n\n/**\n * Implements tasks that run locally without cross-host collaboration.\n *\n * @private\n */\nexport class LocalTaskWorker {\n private abortWait: AbortController | undefined;\n private taskAbortController: AbortController | undefined;\n #taskState: Exclude<TaskApiTasksResponse['taskState'], null> = {\n status: 'idle',\n };\n #workerState: TaskApiTasksResponse['workerState'] = {\n status: 'idle',\n };\n\n private readonly taskId: string;\n private readonly fn: SchedulerServiceTaskFunction;\n private readonly logger: LoggerService;\n\n constructor(\n taskId: string,\n fn: SchedulerServiceTaskFunction,\n logger: LoggerService,\n ) {\n this.taskId = taskId;\n this.fn = fn;\n this.logger = logger;\n }\n\n start(settings: TaskSettingsV2, options: { signal: AbortSignal }) {\n this.logger.info(\n `Registered scheduled task: ${this.taskId}, ${JSON.stringify(settings)}`,\n );\n\n (async () => {\n let attemptNum = 1;\n for (;;) {\n try {\n await this.performInitialWait(settings, options.signal);\n\n while (!options.signal.aborted) {\n const startTime = process.hrtime();\n await this.runOnce(settings, options.signal);\n const timeTaken = process.hrtime(startTime);\n await this.waitUntilNext(\n settings,\n (timeTaken[0] + timeTaken[1] / 1e9) * 1000,\n options.signal,\n );\n }\n\n this.logger.info(`Task worker finished: ${this.taskId}`);\n attemptNum = 0;\n break;\n } catch (e) {\n attemptNum += 1;\n this.logger.warn(\n `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`,\n );\n await sleep(Duration.fromObject({ seconds: 1 }));\n }\n }\n })();\n }\n\n trigger(): void {\n if (!this.abortWait) {\n throw new ConflictError(`Task ${this.taskId} is currently running`);\n }\n this.abortWait.abort();\n }\n\n cancel(): void {\n if (!this.taskAbortController) {\n throw new ConflictError(`Task ${this.taskId} is not running`);\n }\n this.taskAbortController.abort();\n }\n\n taskState(): TaskApiTasksResponse['taskState'] {\n return this.#taskState;\n }\n\n workerState(): TaskApiTasksResponse['workerState'] {\n return this.#workerState;\n }\n\n /**\n * Does the once-at-startup initial wait, if configured.\n */\n private async performInitialWait(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n if (settings.initialDelayDuration) {\n const parsedDuration = Duration.fromISO(settings.initialDelayDuration);\n\n this.#taskState = {\n status: 'idle',\n startsAt: DateTime.utc().plus(parsedDuration).toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'initial-wait',\n };\n\n await this.sleep(parsedDuration, signal);\n }\n }\n\n /**\n * Makes a single attempt at running the task to completion.\n */\n private async runOnce(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n // Abort the task execution either if the worker is stopped, or if the\n // task timeout is hit\n this.taskAbortController = delegateAbortController(signal);\n const timeoutDuration = Duration.fromISO(settings.timeoutAfterDuration);\n const timeoutHandle = setTimeout(() => {\n this.taskAbortController?.abort();\n }, timeoutDuration.as('milliseconds'));\n\n this.#taskState = {\n status: 'running',\n startedAt: DateTime.utc().toISO()!,\n timesOutAt: DateTime.utc().plus(timeoutDuration).toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'running',\n };\n\n try {\n await this.fn(this.taskAbortController.signal);\n this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;\n this.#taskState.lastRunError = undefined;\n } catch (e) {\n this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;\n this.#taskState.lastRunError = serializeError(e);\n }\n\n // release resources\n clearTimeout(timeoutHandle);\n this.taskAbortController.abort();\n this.taskAbortController = undefined;\n }\n\n /**\n * Sleeps until it's time to run the task again.\n */\n private async waitUntilNext(\n settings: TaskSettingsV2,\n lastRunMillis: number,\n signal: AbortSignal,\n ) {\n if (signal.aborted) {\n return;\n }\n\n const isCron = !settings.cadence.startsWith('P');\n let dt: number;\n\n if (isCron) {\n const nextRun = +new CronTime(settings.cadence).sendAt().toJSDate();\n dt = nextRun - Date.now();\n } else {\n dt =\n Duration.fromISO(settings.cadence).as('milliseconds') - lastRunMillis;\n }\n\n dt = Math.max(dt, 0);\n const startsAt = DateTime.now().plus(Duration.fromMillis(dt));\n\n this.#taskState = {\n status: 'idle',\n startsAt: startsAt.toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'idle',\n };\n\n this.logger.debug(\n `task: ${this.taskId} will next occur around ${startsAt}`,\n );\n\n await this.sleep(Duration.fromMillis(dt), signal);\n }\n\n private async sleep(\n duration: Duration,\n abortSignal: AbortSignal,\n ): Promise<void> {\n this.abortWait = delegateAbortController(abortSignal);\n await sleep(duration, this.abortWait.signal);\n this.abortWait.abort(); // cleans up resources\n this.abortWait = undefined;\n }\n}\n"],"names":["sleep","Duration","ConflictError","DateTime","delegateAbortController","serializeError","CronTime"],"mappings":"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"}
@@ -24,23 +24,25 @@ class PluginTaskSchedulerImpl {
24
24
  pluginId;
25
25
  databaseFactory;
26
26
  logger;
27
- constructor(pluginId, databaseFactory, logger, rootLifecycle) {
27
+ constructor(pluginId, databaseFactory, logger, metrics, rootLifecycle) {
28
28
  this.pluginId = pluginId;
29
29
  this.databaseFactory = databaseFactory;
30
30
  this.logger = logger;
31
- const meter = api.metrics.getMeter("default");
32
- this.counter = meter.createCounter("backend_tasks.task.runs.count", {
31
+ this.counter = metrics.createCounter("backend_tasks.task.runs.count", {
33
32
  description: "Total number of times a task has been run"
34
33
  });
35
- this.duration = meter.createHistogram("backend_tasks.task.runs.duration", {
36
- description: "Histogram of task run durations",
37
- unit: "seconds"
38
- });
39
- this.lastStarted = meter.createGauge("backend_tasks.task.runs.started", {
34
+ this.duration = metrics.createHistogram(
35
+ "backend_tasks.task.runs.duration",
36
+ {
37
+ description: "Histogram of task run durations",
38
+ unit: "seconds"
39
+ }
40
+ );
41
+ this.lastStarted = metrics.createGauge("backend_tasks.task.runs.started", {
40
42
  description: "Epoch timestamp seconds when the task was last started",
41
43
  unit: "seconds"
42
44
  });
43
- this.lastCompleted = meter.createGauge(
45
+ this.lastCompleted = metrics.createGauge(
44
46
  "backend_tasks.task.runs.completed",
45
47
  {
46
48
  description: "Epoch timestamp seconds when the task was last completed",
@@ -60,6 +62,15 @@ class PluginTaskSchedulerImpl {
60
62
  const knex = await this.databaseFactory();
61
63
  await TaskWorker.TaskWorker.trigger(knex, id);
62
64
  }
65
+ async cancelTask(id) {
66
+ const localTask = this.localWorkersById.get(id);
67
+ if (localTask) {
68
+ localTask.cancel();
69
+ return;
70
+ }
71
+ const knex = await this.databaseFactory();
72
+ await TaskWorker.TaskWorker.cancel(knex, id);
73
+ }
63
74
  async scheduleTask(task) {
64
75
  util.validateId(task.id);
65
76
  const scope = task.scope ?? "global";
@@ -133,6 +144,14 @@ class PluginTaskSchedulerImpl {
133
144
  res.status(200).end();
134
145
  }
135
146
  );
147
+ router.post(
148
+ "/.backstage/scheduler/v1/tasks/:id/cancel",
149
+ async (req, res) => {
150
+ const { id } = req.params;
151
+ await this.cancelTask(id);
152
+ res.status(200).end();
153
+ }
154
+ );
136
155
  return router;
137
156
  }
138
157
  instrumentedFunction(task, scope) {
@@ -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, Gauge, metrics, trace } from '@opentelemetry/api';\nimport { Knex } from 'knex';\nimport { Duration } from 'luxon';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { LocalTaskWorker } from './LocalTaskWorker';\nimport { TaskWorker } from './TaskWorker';\nimport { TaskSettingsV2, TaskApiTasksResponse } 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 localWorkersById = new Map<string, LocalTaskWorker>();\n private readonly globalWorkersById = new Map<string, TaskWorker>();\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 private readonly pluginId: string;\n private readonly databaseFactory: () => Promise<Knex>;\n private readonly logger: LoggerService;\n\n constructor(\n pluginId: string,\n databaseFactory: () => Promise<Knex>,\n logger: LoggerService,\n rootLifecycle: RootLifecycleService,\n ) {\n this.pluginId = pluginId;\n this.databaseFactory = databaseFactory;\n this.logger = logger;\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.localWorkersById.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 this.globalWorkersById.set(task.id, worker);\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.localWorkersById.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 getRouter(): express.Router {\n const router = Router();\n\n router.get('/.backstage/scheduler/v1/tasks', async (_, res) => {\n const globalState = await TaskWorker.taskStates(\n await this.databaseFactory(),\n );\n\n const tasks = new Array<TaskApiTasksResponse>();\n for (const task of this.allScheduledTasks) {\n tasks.push({\n taskId: task.id,\n pluginId: this.pluginId,\n scope: task.scope,\n settings: task.settings,\n taskState:\n this.localWorkersById.get(task.id)?.taskState() ??\n globalState.get(task.id) ??\n null,\n workerState:\n this.localWorkersById.get(task.id)?.workerState() ??\n this.globalWorkersById.get(task.id)?.workerState() ??\n null,\n });\n }\n\n res.json({ tasks });\n });\n\n router.post(\n '/.backstage/scheduler/v1/tasks/:id/trigger',\n async (req, res) => {\n const { id } = req.params;\n await this.triggerTask(id);\n res.status(200).end();\n },\n );\n\n return router;\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","Router","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 { trace } from '@opentelemetry/api';\nimport {\n MetricsService,\n MetricsServiceCounter,\n MetricsServiceGauge,\n MetricsServiceHistogram,\n} from '@backstage/backend-plugin-api/alpha';\nimport { Knex } from 'knex';\nimport { Duration } from 'luxon';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { LocalTaskWorker } from './LocalTaskWorker';\nimport { TaskWorker } from './TaskWorker';\nimport { TaskSettingsV2, TaskApiTasksResponse } 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 localWorkersById = new Map<string, LocalTaskWorker>();\n private readonly globalWorkersById = new Map<string, TaskWorker>();\n private readonly allScheduledTasks: SchedulerServiceTaskDescriptor[] = [];\n private readonly shutdownInitiated: Promise<boolean>;\n\n private readonly counter: MetricsServiceCounter;\n private readonly duration: MetricsServiceHistogram;\n private readonly lastStarted: MetricsServiceGauge;\n private readonly lastCompleted: MetricsServiceGauge;\n\n private readonly pluginId: string;\n private readonly databaseFactory: () => Promise<Knex>;\n private readonly logger: LoggerService;\n\n constructor(\n pluginId: string,\n databaseFactory: () => Promise<Knex>,\n logger: LoggerService,\n metrics: MetricsService,\n rootLifecycle: RootLifecycleService,\n ) {\n this.pluginId = pluginId;\n this.databaseFactory = databaseFactory;\n this.logger = logger;\n this.counter = metrics.createCounter('backend_tasks.task.runs.count', {\n description: 'Total number of times a task has been run',\n });\n this.duration = metrics.createHistogram(\n 'backend_tasks.task.runs.duration',\n {\n description: 'Histogram of task run durations',\n unit: 'seconds',\n },\n );\n this.lastStarted = metrics.createGauge('backend_tasks.task.runs.started', {\n description: 'Epoch timestamp seconds when the task was last started',\n unit: 'seconds',\n });\n this.lastCompleted = metrics.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.localWorkersById.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 cancelTask(id: string): Promise<void> {\n const localTask = this.localWorkersById.get(id);\n if (localTask) {\n localTask.cancel();\n return;\n }\n\n const knex = await this.databaseFactory();\n await TaskWorker.cancel(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 this.globalWorkersById.set(task.id, worker);\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.localWorkersById.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 getRouter(): express.Router {\n const router = Router();\n\n router.get('/.backstage/scheduler/v1/tasks', async (_, res) => {\n const globalState = await TaskWorker.taskStates(\n await this.databaseFactory(),\n );\n\n const tasks = new Array<TaskApiTasksResponse>();\n for (const task of this.allScheduledTasks) {\n tasks.push({\n taskId: task.id,\n pluginId: this.pluginId,\n scope: task.scope,\n settings: task.settings,\n taskState:\n this.localWorkersById.get(task.id)?.taskState() ??\n globalState.get(task.id) ??\n null,\n workerState:\n this.localWorkersById.get(task.id)?.workerState() ??\n this.globalWorkersById.get(task.id)?.workerState() ??\n null,\n });\n }\n\n res.json({ tasks });\n });\n\n router.post(\n '/.backstage/scheduler/v1/tasks/:id/trigger',\n async (req, res) => {\n const { id } = req.params;\n await this.triggerTask(id);\n res.status(200).end();\n },\n );\n\n router.post(\n '/.backstage/scheduler/v1/tasks/:id/cancel',\n async (req, res) => {\n const { id } = req.params;\n await this.cancelTask(id);\n res.status(200).end();\n },\n );\n\n return router;\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","TaskWorker","validateId","delegateAbortController","LocalTaskWorker","Router","Duration"],"mappings":"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"}
@@ -93,6 +93,28 @@ class TaskWorker {
93
93
  throw new errors.ConflictError(`Task ${taskId} is currently running`);
94
94
  }
95
95
  }
96
+ static async cancel(knex, taskId) {
97
+ const [row] = await knex(tables.DB_TASKS_TABLE).where("id", "=", taskId).select("settings_json", "current_run_ticket");
98
+ if (!row) {
99
+ throw new errors.NotFoundError(`Task ${taskId} does not exist`);
100
+ }
101
+ if (!row.current_run_ticket) {
102
+ throw new errors.ConflictError(`Task ${taskId} is not running`);
103
+ }
104
+ const settings = types.taskSettingsV2Schema.parse(JSON.parse(row.settings_json));
105
+ const nextRun = TaskWorker.computeNextRunStartAt(knex, settings);
106
+ const updatedRows = await knex(tables.DB_TASKS_TABLE).where("id", "=", taskId).where("current_run_ticket", "=", row.current_run_ticket).update({
107
+ next_run_start_at: nextRun,
108
+ current_run_ticket: knex.raw("null"),
109
+ current_run_started_at: knex.raw("null"),
110
+ current_run_expires_at: knex.raw("null"),
111
+ last_run_ended_at: knex.fn.now(),
112
+ last_run_error_json: util.serializeError(new Error("Task was cancelled"))
113
+ });
114
+ if (updatedRows < 1) {
115
+ throw new errors.ConflictError(`Task ${taskId} is not running`);
116
+ }
117
+ }
96
118
  static async taskStates(knex) {
97
119
  const rows = await knex(tables.DB_TASKS_TABLE);
98
120
  return new Map(
@@ -143,6 +165,16 @@ class TaskWorker {
143
165
  const timeoutHandle = setTimeout(() => {
144
166
  taskAbortController.abort();
145
167
  }, luxon.Duration.fromISO(taskSettings.timeoutAfterDuration).as("milliseconds"));
168
+ let livenessHandle;
169
+ const scheduleLivenessCheck = () => {
170
+ livenessHandle = setTimeout(async () => {
171
+ await this.checkLiveness(ticket, taskAbortController);
172
+ if (!taskAbortController.signal.aborted) {
173
+ scheduleLivenessCheck();
174
+ }
175
+ }, this.workCheckFrequency.as("milliseconds"));
176
+ };
177
+ scheduleLivenessCheck();
146
178
  try {
147
179
  this.#workerState = {
148
180
  status: "running"
@@ -158,6 +190,7 @@ class TaskWorker {
158
190
  status: "idle"
159
191
  };
160
192
  clearTimeout(timeoutHandle);
193
+ clearTimeout(livenessHandle);
161
194
  }
162
195
  await this.tryReleaseTask(ticket, taskSettings);
163
196
  return { result: "completed" };
@@ -181,7 +214,7 @@ class TaskWorker {
181
214
  if (isCron) {
182
215
  const time = new cron.CronTime(settings.cadence).sendAt().minus({ seconds: 1 }).toUTC();
183
216
  const timeConverted = luxon.DateTime.fromJSDate(time.toJSDate());
184
- nextStartAt = this.nextRunAtRaw(timeConverted);
217
+ nextStartAt = TaskWorker.nextRunAtRaw(this.knex, timeConverted);
185
218
  startAt ||= nextStartAt;
186
219
  } else if (isManual) {
187
220
  nextStartAt = this.knex.raw("null");
@@ -222,6 +255,26 @@ class TaskWorker {
222
255
  }
223
256
  );
224
257
  }
258
+ /**
259
+ * Checks whether the current task ticket is still valid in the database.
260
+ * If the ticket has been cleared (e.g. by cancellation or janitor cleanup),
261
+ * aborts the task execution.
262
+ */
263
+ async checkLiveness(ticket, taskAbortController) {
264
+ try {
265
+ const [row] = await this.knex(tables.DB_TASKS_TABLE).where("id", "=", this.taskId).select("current_run_ticket");
266
+ if (!row || row.current_run_ticket !== ticket) {
267
+ this.logger.info(
268
+ `Task ticket for "${this.taskId}" is no longer valid; aborting execution`
269
+ );
270
+ taskAbortController.abort();
271
+ }
272
+ } catch (e) {
273
+ this.logger.warn(
274
+ `Failed to check liveness for task "${this.taskId}", ${e}`
275
+ );
276
+ }
277
+ }
225
278
  /**
226
279
  * Check if the task is ready to run
227
280
  */
@@ -274,40 +327,35 @@ class TaskWorker {
274
327
  });
275
328
  return rows === 1;
276
329
  }
277
- async tryReleaseTask(ticket, settings, error) {
330
+ static computeNextRunStartAt(knex, settings) {
278
331
  const isManual = settings?.cadence === "manual";
279
332
  const isDuration = settings?.cadence.startsWith("P");
280
333
  const isCron = !isManual && !isDuration;
281
- let nextRun;
282
334
  if (isCron) {
283
335
  const time = new cron.CronTime(settings.cadence).sendAt().toUTC();
284
- this.logger.debug(`task: ${this.taskId} will next occur around ${time}`);
285
336
  const timeConverted = luxon.DateTime.fromJSDate(time.toJSDate());
286
- nextRun = this.nextRunAtRaw(timeConverted);
287
- } else if (isManual) {
288
- nextRun = this.knex.raw("null");
289
- } else {
290
- const dt = luxon.Duration.fromISO(settings.cadence).as("seconds");
291
- this.logger.debug(
292
- `task: ${this.taskId} will next occur around ${luxon.DateTime.now().plus({
293
- seconds: dt
294
- })}`
337
+ return TaskWorker.nextRunAtRaw(knex, timeConverted);
338
+ }
339
+ if (isManual) {
340
+ return knex.raw("null");
341
+ }
342
+ const dt = luxon.Duration.fromISO(settings.cadence).as("seconds");
343
+ if (knex.client.config.client.includes("sqlite3")) {
344
+ return knex.raw(`max(datetime(next_run_start_at, ?), datetime('now'))`, [
345
+ `+${dt} seconds`
346
+ ]);
347
+ }
348
+ if (knex.client.config.client.includes("mysql")) {
349
+ return knex.raw(
350
+ `greatest(next_run_start_at + interval ${dt} second, now())`
295
351
  );
296
- if (this.knex.client.config.client.includes("sqlite3")) {
297
- nextRun = this.knex.raw(
298
- `max(datetime(next_run_start_at, ?), datetime('now'))`,
299
- [`+${dt} seconds`]
300
- );
301
- } else if (this.knex.client.config.client.includes("mysql")) {
302
- nextRun = this.knex.raw(
303
- `greatest(next_run_start_at + interval ${dt} second, now())`
304
- );
305
- } else {
306
- nextRun = this.knex.raw(
307
- `greatest(next_run_start_at + interval '${dt} seconds', now())`
308
- );
309
- }
310
352
  }
353
+ return knex.raw(
354
+ `greatest(next_run_start_at + interval '${dt} seconds', now())`
355
+ );
356
+ }
357
+ async tryReleaseTask(ticket, settings, error) {
358
+ const nextRun = TaskWorker.computeNextRunStartAt(this.knex, settings);
311
359
  const rows = await this.knex(tables.DB_TASKS_TABLE).where("id", "=", this.taskId).where("current_run_ticket", "=", ticket).update({
312
360
  next_run_start_at: nextRun,
313
361
  current_run_ticket: this.knex.raw("null"),
@@ -318,13 +366,14 @@ class TaskWorker {
318
366
  });
319
367
  return rows === 1;
320
368
  }
321
- nextRunAtRaw(time) {
322
- if (this.knex.client.config.client.includes("sqlite3")) {
323
- return this.knex.raw("datetime(?)", [time.toISO()]);
324
- } else if (this.knex.client.config.client.includes("mysql")) {
325
- return this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]);
369
+ static nextRunAtRaw(knex, time) {
370
+ if (knex.client.config.client.includes("sqlite3")) {
371
+ return knex.raw("datetime(?)", [time.toISO()]);
372
+ }
373
+ if (knex.client.config.client.includes("mysql")) {
374
+ return knex.raw(`?`, [time.toSQL({ includeOffset: false })]);
326
375
  }
327
- return this.knex.raw(`?`, [time.toISO()]);
376
+ return knex.raw(`?`, [time.toISO()]);
328
377
  }
329
378
  }
330
379
 
@@ -1 +1 @@
1
- {"version":3,"file":"TaskWorker.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/TaskWorker.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport { CronTime } from 'cron';\nimport { Knex } from 'knex';\nimport { DateTime, Duration } from 'luxon';\nimport { v4 as uuid } from 'uuid';\nimport { DB_TASKS_TABLE, DbTasksRow } from '../database/tables';\nimport {\n TaskSettingsV2,\n taskSettingsV2Schema,\n TaskApiTasksResponse,\n} from './types';\nimport {\n delegateAbortController,\n nowPlus,\n sleep,\n dbTime,\n serializeError,\n} from './util';\nimport { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api';\n\nconst DEFAULT_WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 });\n\n/**\n * Implements tasks that run across worker hosts, with collaborative locking.\n *\n * @private\n */\nexport class TaskWorker {\n #workerState: TaskApiTasksResponse['workerState'] = {\n status: 'idle',\n };\n private readonly taskId: string;\n private readonly fn: SchedulerServiceTaskFunction;\n private readonly knex: Knex;\n private readonly logger: LoggerService;\n private readonly workCheckFrequency: Duration;\n\n constructor(\n taskId: string,\n fn: SchedulerServiceTaskFunction,\n knex: Knex,\n logger: LoggerService,\n workCheckFrequency: Duration = DEFAULT_WORK_CHECK_FREQUENCY,\n ) {\n this.taskId = taskId;\n this.fn = fn;\n this.knex = knex;\n this.logger = logger;\n this.workCheckFrequency = workCheckFrequency;\n }\n\n async start(settings: TaskSettingsV2, options: { signal: AbortSignal }) {\n try {\n await this.persistTask(settings);\n } catch (e) {\n throw new Error(`Failed to persist task, ${e}`);\n }\n\n this.logger.info(\n `Registered scheduled task: ${this.taskId}, ${JSON.stringify(settings)}`,\n );\n\n let workCheckFrequency = this.workCheckFrequency;\n const isDuration = settings?.cadence.startsWith('P');\n if (isDuration) {\n const cadence = Duration.fromISO(settings.cadence);\n if (cadence < workCheckFrequency) {\n workCheckFrequency = cadence;\n }\n }\n\n (async () => {\n let attemptNum = 1;\n for (;;) {\n try {\n await this.performInitialWait(settings, options.signal);\n\n while (!options.signal.aborted) {\n const runResult = await this.runOnce(options.signal);\n if (runResult.result === 'abort') {\n break;\n }\n await sleep(workCheckFrequency, options.signal);\n }\n\n this.logger.info(`Task worker finished: ${this.taskId}`);\n attemptNum = 0;\n break;\n } catch (e) {\n attemptNum += 1;\n this.logger.warn(\n `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`,\n );\n await sleep(Duration.fromObject({ seconds: 1 }));\n }\n }\n })();\n }\n\n /**\n * Does the once-at-startup initial wait, if configured.\n */\n private async performInitialWait(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n if (settings.initialDelayDuration) {\n this.#workerState = {\n status: 'initial-wait',\n };\n await sleep(Duration.fromISO(settings.initialDelayDuration), signal);\n }\n this.#workerState = {\n status: 'idle',\n };\n }\n\n static async trigger(knex: Knex, taskId: string): Promise<void> {\n // check if task exists\n const rows = await knex<DbTasksRow>(DB_TASKS_TABLE)\n .select(knex.raw(1))\n .where('id', '=', taskId);\n if (rows.length !== 1) {\n throw new NotFoundError(`Task ${taskId} does not exist`);\n }\n\n const updatedRows = await knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', taskId)\n .whereNull('current_run_ticket')\n .update({\n next_run_start_at: knex.fn.now(),\n });\n if (updatedRows < 1) {\n throw new ConflictError(`Task ${taskId} is currently running`);\n }\n }\n\n static async taskStates(\n knex: Knex,\n ): Promise<Map<string, TaskApiTasksResponse['taskState']>> {\n const rows = await knex<DbTasksRow>(DB_TASKS_TABLE);\n return new Map(\n rows.map(row => {\n const startedAt = row.current_run_started_at\n ? dbTime(row.current_run_started_at).toISO()!\n : undefined;\n const timesOutAt = row.current_run_expires_at\n ? dbTime(row.current_run_expires_at).toISO()!\n : undefined;\n const startsAt = row.next_run_start_at\n ? dbTime(row.next_run_start_at).toISO()!\n : undefined;\n const lastRunEndedAt = row.last_run_ended_at\n ? dbTime(row.last_run_ended_at).toISO()!\n : undefined;\n const lastRunError = row.last_run_error_json || undefined;\n\n return [\n row.id,\n startedAt\n ? {\n status: 'running',\n startedAt,\n timesOutAt,\n lastRunEndedAt,\n lastRunError,\n }\n : {\n status: 'idle',\n startsAt,\n lastRunEndedAt,\n lastRunError,\n },\n ];\n }),\n );\n }\n\n workerState(): TaskApiTasksResponse['workerState'] {\n return this.#workerState;\n }\n\n /**\n * Makes a single attempt at running the task to completion, if ready.\n *\n * @returns The outcome of the attempt\n */\n private async runOnce(\n signal: AbortSignal,\n ): Promise<\n | { result: 'not-ready-yet' }\n | { result: 'abort' }\n | { result: 'failed' }\n | { result: 'completed' }\n > {\n const findResult = await this.findReadyTask();\n if (\n findResult.result === 'not-ready-yet' ||\n findResult.result === 'abort'\n ) {\n return findResult;\n }\n\n const taskSettings = findResult.settings;\n const ticket = uuid();\n\n const claimed = await this.tryClaimTask(ticket, taskSettings);\n if (!claimed) {\n return { result: 'not-ready-yet' };\n }\n\n // Abort the task execution either if the worker is stopped, or if the\n // task timeout is hit\n const taskAbortController = delegateAbortController(signal);\n const timeoutHandle = setTimeout(() => {\n taskAbortController.abort();\n }, Duration.fromISO(taskSettings.timeoutAfterDuration).as('milliseconds'));\n\n try {\n this.#workerState = {\n status: 'running',\n };\n await this.fn(taskAbortController.signal);\n taskAbortController.abort(); // releases resources\n } catch (e) {\n this.logger.error(e);\n await this.tryReleaseTask(ticket, taskSettings, e);\n return { result: 'failed' };\n } finally {\n this.#workerState = {\n status: 'idle',\n };\n clearTimeout(timeoutHandle);\n }\n\n await this.tryReleaseTask(ticket, taskSettings);\n return { result: 'completed' };\n }\n\n /**\n * Perform the initial store of the task info\n */\n async persistTask(settings: TaskSettingsV2) {\n // Perform an initial parse to ensure that we will definitely be able to\n // read it back again.\n taskSettingsV2Schema.parse(settings);\n\n const isManual = settings?.cadence === 'manual';\n const isDuration = settings?.cadence.startsWith('P');\n const isCron = !isManual && !isDuration;\n\n let startAt: Knex.Raw | undefined;\n let nextStartAt: Knex.Raw | undefined;\n if (settings.initialDelayDuration) {\n startAt = nowPlus(\n Duration.fromISO(settings.initialDelayDuration),\n this.knex,\n );\n }\n\n if (isCron) {\n const time = new CronTime(settings.cadence)\n .sendAt()\n .minus({ seconds: 1 }) // immediately, if \"* * * * * *\"\n .toUTC();\n // We make a conversion here to make typescript happy, because the luxon versions of the cron library and here may not be the same\n const timeConverted = DateTime.fromJSDate(time.toJSDate());\n\n nextStartAt = this.nextRunAtRaw(timeConverted);\n startAt ||= nextStartAt;\n } else if (isManual) {\n nextStartAt = this.knex.raw('null');\n startAt ||= nextStartAt;\n } else {\n startAt ||= this.knex.fn.now();\n nextStartAt = nowPlus(Duration.fromISO(settings.cadence), this.knex);\n }\n\n this.logger.debug(`task: ${this.taskId} configured to run at: ${startAt}`);\n\n // It's OK if the task already exists; if it does, just replace its\n // settings with the new value and start the loop as usual.\n const settingsJson = JSON.stringify(settings);\n await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .insert({\n id: this.taskId,\n settings_json: settingsJson,\n next_run_start_at: startAt,\n })\n .onConflict('id')\n .merge(\n this.knex.client.config.client.includes('mysql')\n ? {\n settings_json: settingsJson,\n next_run_start_at: this.knex.raw(\n `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`,\n [\n nextStartAt,\n 'next_run_start_at',\n nextStartAt,\n 'next_run_start_at',\n ],\n ),\n }\n : {\n settings_json: this.knex.ref('excluded.settings_json'),\n next_run_start_at: this.knex.raw(\n `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`,\n [\n nextStartAt,\n `${DB_TASKS_TABLE}.next_run_start_at`,\n nextStartAt,\n `${DB_TASKS_TABLE}.next_run_start_at`,\n ],\n ),\n },\n );\n }\n\n /**\n * Check if the task is ready to run\n */\n async findReadyTask(): Promise<\n | { result: 'not-ready-yet' }\n | { result: 'abort' }\n | { result: 'ready'; settings: TaskSettingsV2 }\n > {\n const [row] = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .select({\n settingsJson: 'settings_json',\n ready: this.knex.raw(\n `CASE\n WHEN next_run_start_at <= ? AND current_run_ticket IS NULL THEN TRUE\n ELSE FALSE\n END`,\n [this.knex.fn.now()],\n ),\n });\n\n if (!row) {\n this.logger.info(\n 'No longer able to find task; aborting and assuming that it has been unregistered or expired',\n );\n return { result: 'abort' };\n } else if (!row.ready) {\n return { result: 'not-ready-yet' };\n }\n\n try {\n const obj = JSON.parse(row.settingsJson);\n const settings = taskSettingsV2Schema.parse(obj);\n return { result: 'ready', settings };\n } catch (e) {\n this.logger.info(\n `Task \"${this.taskId}\" is no longer able to parse task settings; aborting and assuming that a ` +\n `newer version of the task has been issued and being handled by other workers, ${e}`,\n );\n return { result: 'abort' };\n }\n }\n\n /**\n * Attempts to claim a task that's ready for execution, on this worker's\n * behalf. We should not attempt to perform the work unless the claim really\n * goes through.\n *\n * @param ticket - A globally unique string that changes for each invocation\n * @param settings - The settings of the task to claim\n * @returns True if it was successfully claimed\n */\n async tryClaimTask(\n ticket: string,\n settings: TaskSettingsV2,\n ): Promise<boolean> {\n const startedAt = this.knex.fn.now();\n const expiresAt = settings.timeoutAfterDuration\n ? nowPlus(Duration.fromISO(settings.timeoutAfterDuration), this.knex)\n : this.knex.raw('null');\n\n const rows = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .whereNull('current_run_ticket')\n .update({\n current_run_ticket: ticket,\n current_run_started_at: startedAt,\n current_run_expires_at: expiresAt,\n });\n\n return rows === 1;\n }\n\n async tryReleaseTask(\n ticket: string,\n settings: TaskSettingsV2,\n error?: Error,\n ): Promise<boolean> {\n const isManual = settings?.cadence === 'manual';\n const isDuration = settings?.cadence.startsWith('P');\n const isCron = !isManual && !isDuration;\n\n let nextRun: Knex.Raw;\n if (isCron) {\n const time = new CronTime(settings.cadence).sendAt().toUTC();\n this.logger.debug(`task: ${this.taskId} will next occur around ${time}`);\n // We make a conversion here to make typescript happy, because the luxon versions of the cron library and here may not be the same\n const timeConverted = DateTime.fromJSDate(time.toJSDate());\n\n nextRun = this.nextRunAtRaw(timeConverted);\n } else if (isManual) {\n nextRun = this.knex.raw('null');\n } else {\n const dt = Duration.fromISO(settings.cadence).as('seconds');\n this.logger.debug(\n `task: ${this.taskId} will next occur around ${DateTime.now().plus({\n seconds: dt,\n })}`,\n );\n\n if (this.knex.client.config.client.includes('sqlite3')) {\n nextRun = this.knex.raw(\n `max(datetime(next_run_start_at, ?), datetime('now'))`,\n [`+${dt} seconds`],\n );\n } else if (this.knex.client.config.client.includes('mysql')) {\n nextRun = this.knex.raw(\n `greatest(next_run_start_at + interval ${dt} second, now())`,\n );\n } else {\n nextRun = this.knex.raw(\n `greatest(next_run_start_at + interval '${dt} seconds', now())`,\n );\n }\n }\n\n const rows = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .where('current_run_ticket', '=', ticket)\n .update({\n next_run_start_at: nextRun,\n current_run_ticket: this.knex.raw('null'),\n current_run_started_at: this.knex.raw('null'),\n current_run_expires_at: this.knex.raw('null'),\n last_run_ended_at: this.knex.fn.now(),\n last_run_error_json: error\n ? serializeError(error)\n : this.knex.raw('null'),\n });\n\n return rows === 1;\n }\n\n private nextRunAtRaw(time: DateTime): Knex.Raw {\n if (this.knex.client.config.client.includes('sqlite3')) {\n return this.knex.raw('datetime(?)', [time.toISO()]);\n } else if (this.knex.client.config.client.includes('mysql')) {\n return this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]);\n }\n return this.knex.raw(`?`, [time.toISO()]);\n }\n}\n"],"names":["Duration","sleep","DB_TASKS_TABLE","NotFoundError","ConflictError","dbTime","uuid","delegateAbortController","taskSettingsV2Schema","nowPlus","CronTime","DateTime","serializeError"],"mappings":"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"}
1
+ {"version":3,"file":"TaskWorker.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/TaskWorker.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport { CronTime } from 'cron';\nimport { Knex } from 'knex';\nimport { DateTime, Duration } from 'luxon';\nimport { v4 as uuid } from 'uuid';\nimport { DB_TASKS_TABLE, DbTasksRow } from '../database/tables';\nimport {\n TaskSettingsV2,\n taskSettingsV2Schema,\n TaskApiTasksResponse,\n} from './types';\nimport {\n delegateAbortController,\n nowPlus,\n sleep,\n dbTime,\n serializeError,\n} from './util';\nimport { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api';\n\nconst DEFAULT_WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 });\n\n/**\n * Implements tasks that run across worker hosts, with collaborative locking.\n *\n * @private\n */\nexport class TaskWorker {\n #workerState: TaskApiTasksResponse['workerState'] = {\n status: 'idle',\n };\n private readonly taskId: string;\n private readonly fn: SchedulerServiceTaskFunction;\n private readonly knex: Knex;\n private readonly logger: LoggerService;\n private readonly workCheckFrequency: Duration;\n\n constructor(\n taskId: string,\n fn: SchedulerServiceTaskFunction,\n knex: Knex,\n logger: LoggerService,\n workCheckFrequency: Duration = DEFAULT_WORK_CHECK_FREQUENCY,\n ) {\n this.taskId = taskId;\n this.fn = fn;\n this.knex = knex;\n this.logger = logger;\n this.workCheckFrequency = workCheckFrequency;\n }\n\n async start(settings: TaskSettingsV2, options: { signal: AbortSignal }) {\n try {\n await this.persistTask(settings);\n } catch (e) {\n throw new Error(`Failed to persist task, ${e}`);\n }\n\n this.logger.info(\n `Registered scheduled task: ${this.taskId}, ${JSON.stringify(settings)}`,\n );\n\n let workCheckFrequency = this.workCheckFrequency;\n const isDuration = settings?.cadence.startsWith('P');\n if (isDuration) {\n const cadence = Duration.fromISO(settings.cadence);\n if (cadence < workCheckFrequency) {\n workCheckFrequency = cadence;\n }\n }\n\n (async () => {\n let attemptNum = 1;\n for (;;) {\n try {\n await this.performInitialWait(settings, options.signal);\n\n while (!options.signal.aborted) {\n const runResult = await this.runOnce(options.signal);\n if (runResult.result === 'abort') {\n break;\n }\n await sleep(workCheckFrequency, options.signal);\n }\n\n this.logger.info(`Task worker finished: ${this.taskId}`);\n attemptNum = 0;\n break;\n } catch (e) {\n attemptNum += 1;\n this.logger.warn(\n `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`,\n );\n await sleep(Duration.fromObject({ seconds: 1 }));\n }\n }\n })();\n }\n\n /**\n * Does the once-at-startup initial wait, if configured.\n */\n private async performInitialWait(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n if (settings.initialDelayDuration) {\n this.#workerState = {\n status: 'initial-wait',\n };\n await sleep(Duration.fromISO(settings.initialDelayDuration), signal);\n }\n this.#workerState = {\n status: 'idle',\n };\n }\n\n static async trigger(knex: Knex, taskId: string): Promise<void> {\n // check if task exists\n const rows = await knex<DbTasksRow>(DB_TASKS_TABLE)\n .select(knex.raw(1))\n .where('id', '=', taskId);\n if (rows.length !== 1) {\n throw new NotFoundError(`Task ${taskId} does not exist`);\n }\n\n const updatedRows = await knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', taskId)\n .whereNull('current_run_ticket')\n .update({\n next_run_start_at: knex.fn.now(),\n });\n if (updatedRows < 1) {\n throw new ConflictError(`Task ${taskId} is currently running`);\n }\n }\n\n static async cancel(knex: Knex, taskId: string): Promise<void> {\n const [row] = await knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', taskId)\n .select('settings_json', 'current_run_ticket');\n if (!row) {\n throw new NotFoundError(`Task ${taskId} does not exist`);\n }\n if (!row.current_run_ticket) {\n throw new ConflictError(`Task ${taskId} is not running`);\n }\n\n const settings = taskSettingsV2Schema.parse(JSON.parse(row.settings_json));\n const nextRun = TaskWorker.computeNextRunStartAt(knex, settings);\n\n const updatedRows = await knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', taskId)\n .where('current_run_ticket', '=', row.current_run_ticket)\n .update({\n next_run_start_at: nextRun,\n current_run_ticket: knex.raw('null'),\n current_run_started_at: knex.raw('null'),\n current_run_expires_at: knex.raw('null'),\n last_run_ended_at: knex.fn.now(),\n last_run_error_json: serializeError(new Error('Task was cancelled')),\n });\n if (updatedRows < 1) {\n throw new ConflictError(`Task ${taskId} is not running`);\n }\n }\n\n static async taskStates(\n knex: Knex,\n ): Promise<Map<string, TaskApiTasksResponse['taskState']>> {\n const rows = await knex<DbTasksRow>(DB_TASKS_TABLE);\n return new Map(\n rows.map(row => {\n const startedAt = row.current_run_started_at\n ? dbTime(row.current_run_started_at).toISO()!\n : undefined;\n const timesOutAt = row.current_run_expires_at\n ? dbTime(row.current_run_expires_at).toISO()!\n : undefined;\n const startsAt = row.next_run_start_at\n ? dbTime(row.next_run_start_at).toISO()!\n : undefined;\n const lastRunEndedAt = row.last_run_ended_at\n ? dbTime(row.last_run_ended_at).toISO()!\n : undefined;\n const lastRunError = row.last_run_error_json || undefined;\n\n return [\n row.id,\n startedAt\n ? {\n status: 'running',\n startedAt,\n timesOutAt,\n lastRunEndedAt,\n lastRunError,\n }\n : {\n status: 'idle',\n startsAt,\n lastRunEndedAt,\n lastRunError,\n },\n ];\n }),\n );\n }\n\n workerState(): TaskApiTasksResponse['workerState'] {\n return this.#workerState;\n }\n\n /**\n * Makes a single attempt at running the task to completion, if ready.\n *\n * @returns The outcome of the attempt\n */\n private async runOnce(\n signal: AbortSignal,\n ): Promise<\n | { result: 'not-ready-yet' }\n | { result: 'abort' }\n | { result: 'failed' }\n | { result: 'completed' }\n > {\n const findResult = await this.findReadyTask();\n if (\n findResult.result === 'not-ready-yet' ||\n findResult.result === 'abort'\n ) {\n return findResult;\n }\n\n const taskSettings = findResult.settings;\n const ticket = uuid();\n\n const claimed = await this.tryClaimTask(ticket, taskSettings);\n if (!claimed) {\n return { result: 'not-ready-yet' };\n }\n\n // Abort the task execution either if the worker is stopped, or if the\n // task timeout is hit, or if the task ticket was lost (e.g. due to\n // cancellation from another host)\n const taskAbortController = delegateAbortController(signal);\n const timeoutHandle = setTimeout(() => {\n taskAbortController.abort();\n }, Duration.fromISO(taskSettings.timeoutAfterDuration).as('milliseconds'));\n let livenessHandle: ReturnType<typeof setTimeout> | undefined;\n const scheduleLivenessCheck = () => {\n livenessHandle = setTimeout(async () => {\n await this.checkLiveness(ticket, taskAbortController);\n if (!taskAbortController.signal.aborted) {\n scheduleLivenessCheck();\n }\n }, this.workCheckFrequency.as('milliseconds'));\n };\n scheduleLivenessCheck();\n\n try {\n this.#workerState = {\n status: 'running',\n };\n await this.fn(taskAbortController.signal);\n taskAbortController.abort(); // releases resources\n } catch (e) {\n this.logger.error(e);\n await this.tryReleaseTask(ticket, taskSettings, e);\n return { result: 'failed' };\n } finally {\n this.#workerState = {\n status: 'idle',\n };\n clearTimeout(timeoutHandle);\n clearTimeout(livenessHandle);\n }\n\n await this.tryReleaseTask(ticket, taskSettings);\n return { result: 'completed' };\n }\n\n /**\n * Perform the initial store of the task info\n */\n async persistTask(settings: TaskSettingsV2) {\n // Perform an initial parse to ensure that we will definitely be able to\n // read it back again.\n taskSettingsV2Schema.parse(settings);\n\n const isManual = settings?.cadence === 'manual';\n const isDuration = settings?.cadence.startsWith('P');\n const isCron = !isManual && !isDuration;\n\n let startAt: Knex.Raw | undefined;\n let nextStartAt: Knex.Raw | undefined;\n if (settings.initialDelayDuration) {\n startAt = nowPlus(\n Duration.fromISO(settings.initialDelayDuration),\n this.knex,\n );\n }\n\n if (isCron) {\n const time = new CronTime(settings.cadence)\n .sendAt()\n .minus({ seconds: 1 }) // immediately, if \"* * * * * *\"\n .toUTC();\n // We make a conversion here to make typescript happy, because the luxon versions of the cron library and here may not be the same\n const timeConverted = DateTime.fromJSDate(time.toJSDate());\n\n nextStartAt = TaskWorker.nextRunAtRaw(this.knex, timeConverted);\n startAt ||= nextStartAt;\n } else if (isManual) {\n nextStartAt = this.knex.raw('null');\n startAt ||= nextStartAt;\n } else {\n startAt ||= this.knex.fn.now();\n nextStartAt = nowPlus(Duration.fromISO(settings.cadence), this.knex);\n }\n\n this.logger.debug(`task: ${this.taskId} configured to run at: ${startAt}`);\n\n // It's OK if the task already exists; if it does, just replace its\n // settings with the new value and start the loop as usual.\n const settingsJson = JSON.stringify(settings);\n await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .insert({\n id: this.taskId,\n settings_json: settingsJson,\n next_run_start_at: startAt,\n })\n .onConflict('id')\n .merge(\n this.knex.client.config.client.includes('mysql')\n ? {\n settings_json: settingsJson,\n next_run_start_at: this.knex.raw(\n `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`,\n [\n nextStartAt,\n 'next_run_start_at',\n nextStartAt,\n 'next_run_start_at',\n ],\n ),\n }\n : {\n settings_json: this.knex.ref('excluded.settings_json'),\n next_run_start_at: this.knex.raw(\n `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`,\n [\n nextStartAt,\n `${DB_TASKS_TABLE}.next_run_start_at`,\n nextStartAt,\n `${DB_TASKS_TABLE}.next_run_start_at`,\n ],\n ),\n },\n );\n }\n\n /**\n * Checks whether the current task ticket is still valid in the database.\n * If the ticket has been cleared (e.g. by cancellation or janitor cleanup),\n * aborts the task execution.\n */\n private async checkLiveness(\n ticket: string,\n taskAbortController: AbortController,\n ): Promise<void> {\n try {\n const [row] = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .select('current_run_ticket');\n\n if (!row || row.current_run_ticket !== ticket) {\n this.logger.info(\n `Task ticket for \"${this.taskId}\" is no longer valid; aborting execution`,\n );\n taskAbortController.abort();\n }\n } catch (e) {\n this.logger.warn(\n `Failed to check liveness for task \"${this.taskId}\", ${e}`,\n );\n }\n }\n\n /**\n * Check if the task is ready to run\n */\n async findReadyTask(): Promise<\n | { result: 'not-ready-yet' }\n | { result: 'abort' }\n | { result: 'ready'; settings: TaskSettingsV2 }\n > {\n const [row] = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .select({\n settingsJson: 'settings_json',\n ready: this.knex.raw(\n `CASE\n WHEN next_run_start_at <= ? AND current_run_ticket IS NULL THEN TRUE\n ELSE FALSE\n END`,\n [this.knex.fn.now()],\n ),\n });\n\n if (!row) {\n this.logger.info(\n 'No longer able to find task; aborting and assuming that it has been unregistered or expired',\n );\n return { result: 'abort' };\n } else if (!row.ready) {\n return { result: 'not-ready-yet' };\n }\n\n try {\n const obj = JSON.parse(row.settingsJson);\n const settings = taskSettingsV2Schema.parse(obj);\n return { result: 'ready', settings };\n } catch (e) {\n this.logger.info(\n `Task \"${this.taskId}\" is no longer able to parse task settings; aborting and assuming that a ` +\n `newer version of the task has been issued and being handled by other workers, ${e}`,\n );\n return { result: 'abort' };\n }\n }\n\n /**\n * Attempts to claim a task that's ready for execution, on this worker's\n * behalf. We should not attempt to perform the work unless the claim really\n * goes through.\n *\n * @param ticket - A globally unique string that changes for each invocation\n * @param settings - The settings of the task to claim\n * @returns True if it was successfully claimed\n */\n async tryClaimTask(\n ticket: string,\n settings: TaskSettingsV2,\n ): Promise<boolean> {\n const startedAt = this.knex.fn.now();\n const expiresAt = settings.timeoutAfterDuration\n ? nowPlus(Duration.fromISO(settings.timeoutAfterDuration), this.knex)\n : this.knex.raw('null');\n\n const rows = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .whereNull('current_run_ticket')\n .update({\n current_run_ticket: ticket,\n current_run_started_at: startedAt,\n current_run_expires_at: expiresAt,\n });\n\n return rows === 1;\n }\n\n private static computeNextRunStartAt(\n knex: Knex,\n settings: TaskSettingsV2,\n ): Knex.Raw {\n const isManual = settings?.cadence === 'manual';\n const isDuration = settings?.cadence.startsWith('P');\n const isCron = !isManual && !isDuration;\n\n if (isCron) {\n const time = new CronTime(settings.cadence).sendAt().toUTC();\n const timeConverted = DateTime.fromJSDate(time.toJSDate());\n return TaskWorker.nextRunAtRaw(knex, timeConverted);\n }\n\n if (isManual) {\n return knex.raw('null');\n }\n\n const dt = Duration.fromISO(settings.cadence).as('seconds');\n\n if (knex.client.config.client.includes('sqlite3')) {\n return knex.raw(`max(datetime(next_run_start_at, ?), datetime('now'))`, [\n `+${dt} seconds`,\n ]);\n }\n\n if (knex.client.config.client.includes('mysql')) {\n return knex.raw(\n `greatest(next_run_start_at + interval ${dt} second, now())`,\n );\n }\n\n return knex.raw(\n `greatest(next_run_start_at + interval '${dt} seconds', now())`,\n );\n }\n\n async tryReleaseTask(\n ticket: string,\n settings: TaskSettingsV2,\n error?: Error,\n ): Promise<boolean> {\n const nextRun = TaskWorker.computeNextRunStartAt(this.knex, settings);\n\n const rows = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .where('current_run_ticket', '=', ticket)\n .update({\n next_run_start_at: nextRun,\n current_run_ticket: this.knex.raw('null'),\n current_run_started_at: this.knex.raw('null'),\n current_run_expires_at: this.knex.raw('null'),\n last_run_ended_at: this.knex.fn.now(),\n last_run_error_json: error\n ? serializeError(error)\n : this.knex.raw('null'),\n });\n\n return rows === 1;\n }\n\n private static nextRunAtRaw(knex: Knex, time: DateTime): Knex.Raw {\n if (knex.client.config.client.includes('sqlite3')) {\n return knex.raw('datetime(?)', [time.toISO()]);\n }\n if (knex.client.config.client.includes('mysql')) {\n return knex.raw(`?`, [time.toSQL({ includeOffset: false })]);\n }\n return knex.raw(`?`, [time.toISO()]);\n }\n}\n"],"names":["Duration","sleep","DB_TASKS_TABLE","NotFoundError","ConflictError","taskSettingsV2Schema","serializeError","dbTime","uuid","delegateAbortController","nowPlus","CronTime","DateTime"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var alpha = require('@backstage/backend-plugin-api/alpha');
4
5
  var DefaultSchedulerService = require('./lib/DefaultSchedulerService.cjs.js');
5
6
 
6
7
  const schedulerServiceFactory = backendPluginApi.createServiceFactory({
@@ -10,21 +11,24 @@ const schedulerServiceFactory = backendPluginApi.createServiceFactory({
10
11
  logger: backendPluginApi.coreServices.logger,
11
12
  rootLifecycle: backendPluginApi.coreServices.rootLifecycle,
12
13
  httpRouter: backendPluginApi.coreServices.httpRouter,
13
- pluginMetadata: backendPluginApi.coreServices.pluginMetadata
14
+ pluginMetadata: backendPluginApi.coreServices.pluginMetadata,
15
+ metrics: alpha.metricsServiceRef
14
16
  },
15
17
  async factory({
16
18
  database,
17
19
  logger,
18
20
  rootLifecycle,
19
21
  httpRouter,
20
- pluginMetadata
22
+ pluginMetadata,
23
+ metrics
21
24
  }) {
22
25
  return DefaultSchedulerService.DefaultSchedulerService.create({
23
26
  database,
24
27
  logger,
25
28
  rootLifecycle,
26
29
  httpRouter,
27
- pluginMetadata
30
+ pluginMetadata,
31
+ metrics
28
32
  });
29
33
  }
30
34
  });
@@ -1 +1 @@
1
- {"version":3,"file":"schedulerServiceFactory.cjs.js","sources":["../../../src/entrypoints/scheduler/schedulerServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { DefaultSchedulerService } from './lib/DefaultSchedulerService';\n\n/**\n * Scheduling of distributed background tasks.\n *\n * See {@link @backstage/code-plugin-api#SchedulerService}\n * and {@link https://backstage.io/docs/backend-system/core-services/scheduler | the service docs}\n * for more information.\n *\n * @public\n */\nexport const schedulerServiceFactory = createServiceFactory({\n service: coreServices.scheduler,\n deps: {\n database: coreServices.database,\n logger: coreServices.logger,\n rootLifecycle: coreServices.rootLifecycle,\n httpRouter: coreServices.httpRouter,\n pluginMetadata: coreServices.pluginMetadata,\n },\n async factory({\n database,\n logger,\n rootLifecycle,\n httpRouter,\n pluginMetadata,\n }) {\n return DefaultSchedulerService.create({\n database,\n logger,\n rootLifecycle,\n httpRouter,\n pluginMetadata,\n });\n },\n});\n"],"names":["createServiceFactory","coreServices","DefaultSchedulerService"],"mappings":";;;;;AA+BO,MAAM,0BAA0BA,qCAAA,CAAqB;AAAA,EAC1D,SAASC,6BAAA,CAAa,SAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,UAAUA,6BAAA,CAAa,QAAA;AAAA,IACvB,QAAQA,6BAAA,CAAa,MAAA;AAAA,IACrB,eAAeA,6BAAA,CAAa,aAAA;AAAA,IAC5B,YAAYA,6BAAA,CAAa,UAAA;AAAA,IACzB,gBAAgBA,6BAAA,CAAa;AAAA,GAC/B;AAAA,EACA,MAAM,OAAA,CAAQ;AAAA,IACZ,QAAA;AAAA,IACA,MAAA;AAAA,IACA,aAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACF,EAAG;AACD,IAAA,OAAOC,gDAAwB,MAAA,CAAO;AAAA,MACpC,QAAA;AAAA,MACA,MAAA;AAAA,MACA,aAAA;AAAA,MACA,UAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"schedulerServiceFactory.cjs.js","sources":["../../../src/entrypoints/scheduler/schedulerServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { metricsServiceRef } from '@backstage/backend-plugin-api/alpha';\nimport { DefaultSchedulerService } from './lib/DefaultSchedulerService';\n\n/**\n * Scheduling of distributed background tasks.\n *\n * See {@link @backstage/code-plugin-api#SchedulerService}\n * and {@link https://backstage.io/docs/backend-system/core-services/scheduler | the service docs}\n * for more information.\n *\n * @public\n */\nexport const schedulerServiceFactory = createServiceFactory({\n service: coreServices.scheduler,\n deps: {\n database: coreServices.database,\n logger: coreServices.logger,\n rootLifecycle: coreServices.rootLifecycle,\n httpRouter: coreServices.httpRouter,\n pluginMetadata: coreServices.pluginMetadata,\n metrics: metricsServiceRef,\n },\n async factory({\n database,\n logger,\n rootLifecycle,\n httpRouter,\n pluginMetadata,\n metrics,\n }) {\n return DefaultSchedulerService.create({\n database,\n logger,\n rootLifecycle,\n httpRouter,\n pluginMetadata,\n metrics,\n });\n },\n});\n"],"names":["createServiceFactory","coreServices","metricsServiceRef","DefaultSchedulerService"],"mappings":";;;;;;AAgCO,MAAM,0BAA0BA,qCAAA,CAAqB;AAAA,EAC1D,SAASC,6BAAA,CAAa,SAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,UAAUA,6BAAA,CAAa,QAAA;AAAA,IACvB,QAAQA,6BAAA,CAAa,MAAA;AAAA,IACrB,eAAeA,6BAAA,CAAa,aAAA;AAAA,IAC5B,YAAYA,6BAAA,CAAa,UAAA;AAAA,IACzB,gBAAgBA,6BAAA,CAAa,cAAA;AAAA,IAC7B,OAAA,EAASC;AAAA,GACX;AAAA,EACA,MAAM,OAAA,CAAQ;AAAA,IACZ,QAAA;AAAA,IACA,MAAA;AAAA,IACA,aAAA;AAAA,IACA,UAAA;AAAA,IACA,cAAA;AAAA,IACA;AAAA,GACF,EAAG;AACD,IAAA,OAAOC,gDAAwB,MAAA,CAAO;AAAA,MACpC,QAAA;AAAA,MACA,MAAA;AAAA,MACA,aAAA;AAAA,MACA,UAAA;AAAA,MACA,cAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
@@ -4,7 +4,6 @@ var UrlReaderPredicateMux = require('./UrlReaderPredicateMux.cjs.js');
4
4
  var AzureUrlReader = require('./AzureUrlReader.cjs.js');
5
5
  var BitbucketCloudUrlReader = require('./BitbucketCloudUrlReader.cjs.js');
6
6
  var BitbucketServerUrlReader = require('./BitbucketServerUrlReader.cjs.js');
7
- var BitbucketUrlReader = require('./BitbucketUrlReader.cjs.js');
8
7
  var GerritUrlReader = require('./GerritUrlReader.cjs.js');
9
8
  var GithubUrlReader = require('./GithubUrlReader.cjs.js');
10
9
  var GitlabUrlReader = require('./GitlabUrlReader.cjs.js');
@@ -50,7 +49,6 @@ class UrlReaders {
50
49
  AzureUrlReader.AzureUrlReader.factory,
51
50
  BitbucketCloudUrlReader.BitbucketCloudUrlReader.factory,
52
51
  BitbucketServerUrlReader.BitbucketServerUrlReader.factory,
53
- BitbucketUrlReader.BitbucketUrlReader.factory,
54
52
  GerritUrlReader.GerritUrlReader.factory,
55
53
  GithubUrlReader.GithubUrlReader.factory,
56
54
  GiteaUrlReader.GiteaUrlReader.factory,
@@ -1 +1 @@
1
- {"version":3,"file":"UrlReaders.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/UrlReaders.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 LoggerService,\n RootConfigService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport { ReaderFactory } from './types';\nimport { UrlReaderPredicateMux } from './UrlReaderPredicateMux';\nimport { AzureUrlReader } from './AzureUrlReader';\nimport { BitbucketCloudUrlReader } from './BitbucketCloudUrlReader';\nimport { BitbucketServerUrlReader } from './BitbucketServerUrlReader';\nimport { BitbucketUrlReader } from './BitbucketUrlReader';\nimport { GerritUrlReader } from './GerritUrlReader';\nimport { GithubUrlReader } from './GithubUrlReader';\nimport { GitlabUrlReader } from './GitlabUrlReader';\nimport { DefaultReadTreeResponseFactory } from './tree';\nimport { FetchUrlReader } from './FetchUrlReader';\nimport { GoogleGcsUrlReader } from './GoogleGcsUrlReader';\nimport { AwsS3UrlReader } from './AwsS3UrlReader';\nimport { GiteaUrlReader } from './GiteaUrlReader';\nimport { AwsCodeCommitUrlReader } from './AwsCodeCommitUrlReader';\nimport { HarnessUrlReader } from './HarnessUrlReader';\nimport { AzureBlobStorageUrlReader } from './AzureBlobStorageUrlReader';\n\n/**\n * Creation options for {@link @backstage/backend-plugin-api#UrlReaderService}.\n *\n * @public\n */\nexport type UrlReadersOptions = {\n /** Root config object */\n config: RootConfigService;\n /** Logger used by all the readers */\n logger: LoggerService;\n /** A list of factories used to construct individual readers that match on URLs */\n factories?: ReaderFactory[];\n};\n\n/**\n * Helps construct {@link @backstage/backend-plugin-api#UrlReaderService}s.\n *\n * @public\n */\nexport class UrlReaders {\n /**\n * Creates a custom {@link @backstage/backend-plugin-api#UrlReaderService} wrapper for your own set of factories.\n */\n static create(options: UrlReadersOptions): UrlReaderService {\n const { logger, config, factories } = options;\n const mux = new UrlReaderPredicateMux();\n const treeResponseFactory = DefaultReadTreeResponseFactory.create({\n config,\n });\n for (const factory of factories ?? []) {\n const tuples = factory({ config, logger: logger, treeResponseFactory });\n\n for (const tuple of tuples) {\n mux.register(tuple);\n }\n }\n\n return mux;\n }\n\n /**\n * Creates a {@link @backstage/backend-plugin-api#UrlReaderService} wrapper that includes all the default factories\n * from this package.\n *\n * Any additional factories passed will be loaded before the default ones.\n */\n static default(options: UrlReadersOptions) {\n const { logger, config, factories = [] } = options;\n return UrlReaders.create({\n logger,\n config,\n factories: factories.concat([\n AzureUrlReader.factory,\n BitbucketCloudUrlReader.factory,\n BitbucketServerUrlReader.factory,\n BitbucketUrlReader.factory,\n GerritUrlReader.factory,\n GithubUrlReader.factory,\n GiteaUrlReader.factory,\n GitlabUrlReader.factory,\n GoogleGcsUrlReader.factory,\n HarnessUrlReader.factory,\n AwsS3UrlReader.factory,\n AzureBlobStorageUrlReader.factory,\n AwsCodeCommitUrlReader.factory,\n FetchUrlReader.factory,\n ]),\n });\n }\n}\n"],"names":["UrlReaderPredicateMux","DefaultReadTreeResponseFactory","AzureUrlReader","BitbucketCloudUrlReader","BitbucketServerUrlReader","BitbucketUrlReader","GerritUrlReader","GithubUrlReader","GiteaUrlReader","GitlabUrlReader","GoogleGcsUrlReader","HarnessUrlReader","AwsS3UrlReader","AzureBlobStorageUrlReader","AwsCodeCommitUrlReader","FetchUrlReader"],"mappings":";;;;;;;;;;;;;;;;;;;AA0DO,MAAM,UAAA,CAAW;AAAA;AAAA;AAAA;AAAA,EAItB,OAAO,OAAO,OAAA,EAA8C;AAC1D,IAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAA,EAAU,GAAI,OAAA;AACtC,IAAA,MAAM,GAAA,GAAM,IAAIA,2CAAA,EAAsB;AACtC,IAAA,MAAM,mBAAA,GAAsBC,uDAA+B,MAAA,CAAO;AAAA,MAChE;AAAA,KACD,CAAA;AACD,IAAA,KAAA,MAAW,OAAA,IAAW,SAAA,IAAa,EAAC,EAAG;AACrC,MAAA,MAAM,SAAS,OAAA,CAAQ,EAAE,MAAA,EAAQ,MAAA,EAAgB,qBAAqB,CAAA;AAEtE,MAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,QAAA,GAAA,CAAI,SAAS,KAAK,CAAA;AAAA,MACpB;AAAA,IACF;AAEA,IAAA,OAAO,GAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,QAAQ,OAAA,EAA4B;AACzC,IAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAA,GAAY,IAAG,GAAI,OAAA;AAC3C,IAAA,OAAO,WAAW,MAAA,CAAO;AAAA,MACvB,MAAA;AAAA,MACA,MAAA;AAAA,MACA,SAAA,EAAW,UAAU,MAAA,CAAO;AAAA,QAC1BC,6BAAA,CAAe,OAAA;AAAA,QACfC,+CAAA,CAAwB,OAAA;AAAA,QACxBC,iDAAA,CAAyB,OAAA;AAAA,QACzBC,qCAAA,CAAmB,OAAA;AAAA,QACnBC,+BAAA,CAAgB,OAAA;AAAA,QAChBC,+BAAA,CAAgB,OAAA;AAAA,QAChBC,6BAAA,CAAe,OAAA;AAAA,QACfC,+BAAA,CAAgB,OAAA;AAAA,QAChBC,qCAAA,CAAmB,OAAA;AAAA,QACnBC,iCAAA,CAAiB,OAAA;AAAA,QACjBC,6BAAA,CAAe,OAAA;AAAA,QACfC,mDAAA,CAA0B,OAAA;AAAA,QAC1BC,6CAAA,CAAuB,OAAA;AAAA,QACvBC,6BAAA,CAAe;AAAA,OAChB;AAAA,KACF,CAAA;AAAA,EACH;AACF;;;;"}
1
+ {"version":3,"file":"UrlReaders.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/UrlReaders.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 LoggerService,\n RootConfigService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport { ReaderFactory } from './types';\nimport { UrlReaderPredicateMux } from './UrlReaderPredicateMux';\nimport { AzureUrlReader } from './AzureUrlReader';\nimport { BitbucketCloudUrlReader } from './BitbucketCloudUrlReader';\nimport { BitbucketServerUrlReader } from './BitbucketServerUrlReader';\nimport { GerritUrlReader } from './GerritUrlReader';\nimport { GithubUrlReader } from './GithubUrlReader';\nimport { GitlabUrlReader } from './GitlabUrlReader';\nimport { DefaultReadTreeResponseFactory } from './tree';\nimport { FetchUrlReader } from './FetchUrlReader';\nimport { GoogleGcsUrlReader } from './GoogleGcsUrlReader';\nimport { AwsS3UrlReader } from './AwsS3UrlReader';\nimport { GiteaUrlReader } from './GiteaUrlReader';\nimport { AwsCodeCommitUrlReader } from './AwsCodeCommitUrlReader';\nimport { HarnessUrlReader } from './HarnessUrlReader';\nimport { AzureBlobStorageUrlReader } from './AzureBlobStorageUrlReader';\n\n/**\n * Creation options for {@link @backstage/backend-plugin-api#UrlReaderService}.\n *\n * @public\n */\nexport type UrlReadersOptions = {\n /** Root config object */\n config: RootConfigService;\n /** Logger used by all the readers */\n logger: LoggerService;\n /** A list of factories used to construct individual readers that match on URLs */\n factories?: ReaderFactory[];\n};\n\n/**\n * Helps construct {@link @backstage/backend-plugin-api#UrlReaderService}s.\n *\n * @public\n */\nexport class UrlReaders {\n /**\n * Creates a custom {@link @backstage/backend-plugin-api#UrlReaderService} wrapper for your own set of factories.\n */\n static create(options: UrlReadersOptions): UrlReaderService {\n const { logger, config, factories } = options;\n const mux = new UrlReaderPredicateMux();\n const treeResponseFactory = DefaultReadTreeResponseFactory.create({\n config,\n });\n for (const factory of factories ?? []) {\n const tuples = factory({ config, logger: logger, treeResponseFactory });\n\n for (const tuple of tuples) {\n mux.register(tuple);\n }\n }\n\n return mux;\n }\n\n /**\n * Creates a {@link @backstage/backend-plugin-api#UrlReaderService} wrapper that includes all the default factories\n * from this package.\n *\n * Any additional factories passed will be loaded before the default ones.\n */\n static default(options: UrlReadersOptions) {\n const { logger, config, factories = [] } = options;\n return UrlReaders.create({\n logger,\n config,\n factories: factories.concat([\n AzureUrlReader.factory,\n BitbucketCloudUrlReader.factory,\n BitbucketServerUrlReader.factory,\n GerritUrlReader.factory,\n GithubUrlReader.factory,\n GiteaUrlReader.factory,\n GitlabUrlReader.factory,\n GoogleGcsUrlReader.factory,\n HarnessUrlReader.factory,\n AwsS3UrlReader.factory,\n AzureBlobStorageUrlReader.factory,\n AwsCodeCommitUrlReader.factory,\n FetchUrlReader.factory,\n ]),\n });\n }\n}\n"],"names":["UrlReaderPredicateMux","DefaultReadTreeResponseFactory","AzureUrlReader","BitbucketCloudUrlReader","BitbucketServerUrlReader","GerritUrlReader","GithubUrlReader","GiteaUrlReader","GitlabUrlReader","GoogleGcsUrlReader","HarnessUrlReader","AwsS3UrlReader","AzureBlobStorageUrlReader","AwsCodeCommitUrlReader","FetchUrlReader"],"mappings":";;;;;;;;;;;;;;;;;;AAyDO,MAAM,UAAA,CAAW;AAAA;AAAA;AAAA;AAAA,EAItB,OAAO,OAAO,OAAA,EAA8C;AAC1D,IAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAA,EAAU,GAAI,OAAA;AACtC,IAAA,MAAM,GAAA,GAAM,IAAIA,2CAAA,EAAsB;AACtC,IAAA,MAAM,mBAAA,GAAsBC,uDAA+B,MAAA,CAAO;AAAA,MAChE;AAAA,KACD,CAAA;AACD,IAAA,KAAA,MAAW,OAAA,IAAW,SAAA,IAAa,EAAC,EAAG;AACrC,MAAA,MAAM,SAAS,OAAA,CAAQ,EAAE,MAAA,EAAQ,MAAA,EAAgB,qBAAqB,CAAA;AAEtE,MAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,QAAA,GAAA,CAAI,SAAS,KAAK,CAAA;AAAA,MACpB;AAAA,IACF;AAEA,IAAA,OAAO,GAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,QAAQ,OAAA,EAA4B;AACzC,IAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAA,GAAY,IAAG,GAAI,OAAA;AAC3C,IAAA,OAAO,WAAW,MAAA,CAAO;AAAA,MACvB,MAAA;AAAA,MACA,MAAA;AAAA,MACA,SAAA,EAAW,UAAU,MAAA,CAAO;AAAA,QAC1BC,6BAAA,CAAe,OAAA;AAAA,QACfC,+CAAA,CAAwB,OAAA;AAAA,QACxBC,iDAAA,CAAyB,OAAA;AAAA,QACzBC,+BAAA,CAAgB,OAAA;AAAA,QAChBC,+BAAA,CAAgB,OAAA;AAAA,QAChBC,6BAAA,CAAe,OAAA;AAAA,QACfC,+BAAA,CAAgB,OAAA;AAAA,QAChBC,qCAAA,CAAmB,OAAA;AAAA,QACnBC,iCAAA,CAAiB,OAAA;AAAA,QACjBC,6BAAA,CAAe,OAAA;AAAA,QACfC,mDAAA,CAA0B,OAAA;AAAA,QAC1BC,6CAAA,CAAuB,OAAA;AAAA,QACvBC,6BAAA,CAAe;AAAA,OAChB;AAAA,KACF,CAAA;AAAA,EACH;AACF;;;;"}
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var version = "0.15.3-next.0";
5
+ var version = "0.16.0-next.2";
6
6
  var packageinfo = {
7
7
  version: version};
8
8
 
@@ -1,5 +1,6 @@
1
1
  import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
2
  import { DatabaseService, LoggerService, RootLifecycleService, HttpRouterService, PluginMetadataService, SchedulerService } from '@backstage/backend-plugin-api';
3
+ import { MetricsService } from '@backstage/backend-plugin-api/alpha';
3
4
 
4
5
  /**
5
6
  * Default implementation of the task scheduler service.
@@ -10,6 +11,7 @@ declare class DefaultSchedulerService {
10
11
  static create(options: {
11
12
  database: DatabaseService;
12
13
  logger: LoggerService;
14
+ metrics: MetricsService;
13
15
  rootLifecycle: RootLifecycleService;
14
16
  httpRouter: HttpRouterService;
15
17
  pluginMetadata: PluginMetadataService;
@@ -2,7 +2,6 @@
2
2
 
3
3
  var AzureUrlReader = require('./entrypoints/urlReader/lib/AzureUrlReader.cjs.js');
4
4
  var BitbucketCloudUrlReader = require('./entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js');
5
- var BitbucketUrlReader = require('./entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js');
6
5
  var BitbucketServerUrlReader = require('./entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js');
7
6
  var GerritUrlReader = require('./entrypoints/urlReader/lib/GerritUrlReader.cjs.js');
8
7
  var GithubUrlReader = require('./entrypoints/urlReader/lib/GithubUrlReader.cjs.js');
@@ -20,7 +19,6 @@ var urlReaderServiceFactory = require('./entrypoints/urlReader/urlReaderServiceF
20
19
 
21
20
  exports.AzureUrlReader = AzureUrlReader.AzureUrlReader;
22
21
  exports.BitbucketCloudUrlReader = BitbucketCloudUrlReader.BitbucketCloudUrlReader;
23
- exports.BitbucketUrlReader = BitbucketUrlReader.BitbucketUrlReader;
24
22
  exports.BitbucketServerUrlReader = BitbucketServerUrlReader.BitbucketServerUrlReader;
25
23
  exports.GerritUrlReader = GerritUrlReader.GerritUrlReader;
26
24
  exports.GithubUrlReader = GithubUrlReader.GithubUrlReader;
@@ -1 +1 @@
1
- {"version":3,"file":"urlReader.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"urlReader.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,6 +1,6 @@
1
1
  import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
2
  import { RootConfigService, LoggerService, UrlReaderServiceReadTreeResponse, UrlReaderService, UrlReaderServiceReadUrlOptions, UrlReaderServiceReadUrlResponse, UrlReaderServiceReadTreeOptions, UrlReaderServiceSearchOptions, UrlReaderServiceSearchResponse } from '@backstage/backend-plugin-api';
3
- import { AzureIntegration, AzureDevOpsCredentialsProvider, BitbucketCloudIntegration, BitbucketIntegration, BitbucketServerIntegration, GerritIntegration, GithubIntegration, GithubCredentialsProvider, GitLabIntegration, GiteaIntegration, HarnessIntegration, AwsS3Integration, AzureCredentialsManager, AzureBlobStorageIntergation } from '@backstage/integration';
3
+ import { AzureIntegration, AzureDevOpsCredentialsProvider, BitbucketCloudIntegration, BitbucketServerIntegration, GerritIntegration, GithubIntegration, GithubCredentialsProvider, GitLabIntegration, GiteaIntegration, HarnessIntegration, AwsS3Integration, AzureCredentialsManager, AzureBlobStorageIntergation } from '@backstage/integration';
4
4
  import { Readable } from 'node:stream';
5
5
  import { AwsCredentialsManager } from '@backstage/integration-aws-node';
6
6
  import { Config } from '@backstage/config';
@@ -137,29 +137,6 @@ declare class BitbucketCloudUrlReader implements UrlReaderService {
137
137
  private getLastCommitShortHash;
138
138
  }
139
139
 
140
- /**
141
- * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket v1 and v2 APIs, such
142
- * as the one exposed by Bitbucket Cloud itself.
143
- *
144
- * @public
145
- * @deprecated in favor of BitbucketCloudUrlReader and BitbucketServerUrlReader
146
- */
147
- declare class BitbucketUrlReader implements UrlReaderService {
148
- static factory: ReaderFactory;
149
- private readonly integration;
150
- private readonly deps;
151
- constructor(integration: BitbucketIntegration, logger: LoggerService, deps: {
152
- treeResponseFactory: ReadTreeResponseFactory;
153
- });
154
- read(url: string): Promise<Buffer>;
155
- private getCredentials;
156
- readUrl(url: string, options?: UrlReaderServiceReadUrlOptions): Promise<UrlReaderServiceReadUrlResponse>;
157
- readTree(url: string, options?: UrlReaderServiceReadTreeOptions): Promise<UrlReaderServiceReadTreeResponse>;
158
- search(url: string, options?: UrlReaderServiceSearchOptions): Promise<UrlReaderServiceSearchResponse>;
159
- toString(): string;
160
- private getLastCommitShortHash;
161
- }
162
-
163
140
  /**
164
141
  * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket Server APIs.
165
142
  *
@@ -478,5 +455,5 @@ declare const urlReaderFactoriesServiceRef: _backstage_backend_plugin_api.Servic
478
455
  */
479
456
  declare const urlReaderServiceFactory: _backstage_backend_plugin_api.ServiceFactory<_backstage_backend_plugin_api.UrlReaderService, "plugin", "singleton">;
480
457
 
481
- export { AwsS3UrlReader, AzureBlobStorageUrlReader, AzureUrlReader, BitbucketCloudUrlReader, BitbucketServerUrlReader, BitbucketUrlReader, FetchUrlReader, GerritUrlReader, GiteaUrlReader, GithubUrlReader, GitlabUrlReader, HarnessUrlReader, ReadUrlResponseFactory, UrlReaders, urlReaderFactoriesServiceRef, urlReaderServiceFactory };
458
+ export { AwsS3UrlReader, AzureBlobStorageUrlReader, AzureUrlReader, BitbucketCloudUrlReader, BitbucketServerUrlReader, FetchUrlReader, GerritUrlReader, GiteaUrlReader, GithubUrlReader, GitlabUrlReader, HarnessUrlReader, ReadUrlResponseFactory, UrlReaders, urlReaderFactoriesServiceRef, urlReaderServiceFactory };
482
459
  export type { FromReadableArrayOptions, ReadTreeResponseFactory, ReadTreeResponseFactoryOptions, ReadUrlResponseFactoryFromStreamOptions, ReaderFactory, UrlReaderPredicateTuple, UrlReadersOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/backend-defaults",
3
- "version": "0.15.3-next.0",
3
+ "version": "0.16.0-next.2",
4
4
  "description": "Backend defaults used by Backstage backend apps",
5
5
  "backstage": {
6
6
  "role": "node-library"
@@ -217,18 +217,18 @@
217
217
  "@aws-sdk/types": "^3.347.0",
218
218
  "@azure/identity": "^4.0.0",
219
219
  "@azure/storage-blob": "^12.5.0",
220
- "@backstage/backend-app-api": "1.5.1-next.0",
220
+ "@backstage/backend-app-api": "1.6.0-next.1",
221
221
  "@backstage/backend-dev-utils": "0.1.7",
222
- "@backstage/backend-plugin-api": "1.7.1-next.0",
223
- "@backstage/cli-node": "0.2.19-next.0",
222
+ "@backstage/backend-plugin-api": "1.8.0-next.1",
223
+ "@backstage/cli-node": "0.2.19-next.1",
224
224
  "@backstage/config": "1.3.6",
225
225
  "@backstage/config-loader": "1.10.9-next.0",
226
226
  "@backstage/errors": "1.2.7",
227
- "@backstage/integration": "1.21.0-next.0",
227
+ "@backstage/integration": "2.0.0-next.2",
228
228
  "@backstage/integration-aws-node": "0.1.20",
229
- "@backstage/plugin-auth-node": "0.6.14-next.0",
230
- "@backstage/plugin-events-node": "0.4.20-next.0",
231
- "@backstage/plugin-permission-node": "0.10.11-next.0",
229
+ "@backstage/plugin-auth-node": "0.6.14-next.2",
230
+ "@backstage/plugin-events-node": "0.4.20-next.1",
231
+ "@backstage/plugin-permission-node": "0.10.11-next.1",
232
232
  "@backstage/types": "1.2.2",
233
233
  "@google-cloud/storage": "^7.0.0",
234
234
  "@keyv/memcache": "^2.0.1",
@@ -248,7 +248,7 @@
248
248
  "cron": "^3.0.0",
249
249
  "express": "^4.22.0",
250
250
  "express-promise-router": "^4.1.0",
251
- "express-rate-limit": "^7.5.0",
251
+ "express-rate-limit": "^8.2.2",
252
252
  "fs-extra": "^11.2.0",
253
253
  "git-url-parse": "^15.0.0",
254
254
  "helmet": "^6.0.0",
@@ -284,9 +284,9 @@
284
284
  },
285
285
  "devDependencies": {
286
286
  "@aws-sdk/util-stream-node": "^3.350.0",
287
- "@backstage/backend-plugin-api": "1.7.1-next.0",
288
- "@backstage/backend-test-utils": "1.11.1-next.0",
289
- "@backstage/cli": "0.35.5-next.0",
287
+ "@backstage/backend-plugin-api": "1.8.0-next.1",
288
+ "@backstage/backend-test-utils": "1.11.1-next.2",
289
+ "@backstage/cli": "0.36.0-next.2",
290
290
  "@google-cloud/cloud-sql-connector": "^1.4.0",
291
291
  "@types/archiver": "^7.0.0",
292
292
  "@types/base64-stream": "^1.0.2",
@@ -1,209 +0,0 @@
1
- 'use strict';
2
-
3
- var errors = require('@backstage/errors');
4
- var integration = require('@backstage/integration');
5
- var parseGitUrl = require('git-url-parse');
6
- var lodash = require('lodash');
7
- var minimatch = require('minimatch');
8
- var ReadUrlResponseFactory = require('./ReadUrlResponseFactory.cjs.js');
9
-
10
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
-
12
- var parseGitUrl__default = /*#__PURE__*/_interopDefaultCompat(parseGitUrl);
13
-
14
- class BitbucketUrlReader {
15
- static factory = ({ config, logger, treeResponseFactory }) => {
16
- const integrations = integration.ScmIntegrations.fromConfig(config);
17
- return integrations.bitbucket.list().filter(
18
- (item) => !integrations.bitbucketCloud.byHost(item.config.host) && !integrations.bitbucketServer.byHost(item.config.host)
19
- ).map((integration) => {
20
- const reader = new BitbucketUrlReader(integration, logger, {
21
- treeResponseFactory
22
- });
23
- const predicate = (url) => url.host === integration.config.host;
24
- return { reader, predicate };
25
- });
26
- };
27
- integration;
28
- deps;
29
- constructor(integration, logger, deps) {
30
- this.integration = integration;
31
- this.deps = deps;
32
- const { host, token, username, appPassword } = integration.config;
33
- const replacement = host === "bitbucket.org" ? "bitbucketCloud" : "bitbucketServer";
34
- logger.warn(
35
- `[Deprecated] Please migrate from "integrations.bitbucket" to "integrations.${replacement}".`
36
- );
37
- if (!token && username && !appPassword) {
38
- throw new Error(
39
- `Bitbucket integration for '${host}' has configured a username but is missing a required appPassword.`
40
- );
41
- }
42
- }
43
- async read(url) {
44
- const response = await this.readUrl(url);
45
- return response.buffer();
46
- }
47
- getCredentials = async (options) => {
48
- if (options?.token) {
49
- return {
50
- headers: {
51
- Authorization: `Bearer ${options.token}`
52
- }
53
- };
54
- }
55
- return await integration.getBitbucketRequestOptions(this.integration.config);
56
- };
57
- async readUrl(url, options) {
58
- const { etag, lastModifiedAfter, signal } = options ?? {};
59
- const bitbucketUrl = integration.getBitbucketFileFetchUrl(url, this.integration.config);
60
- const requestOptions = await this.getCredentials(options);
61
- let response;
62
- try {
63
- response = await fetch(bitbucketUrl.toString(), {
64
- headers: {
65
- ...requestOptions.headers,
66
- ...etag && { "If-None-Match": etag },
67
- ...lastModifiedAfter && {
68
- "If-Modified-Since": lastModifiedAfter.toUTCString()
69
- }
70
- },
71
- // TODO(freben): The signal cast is there because pre-3.x versions of
72
- // node-fetch have a very slightly deviating AbortSignal type signature.
73
- // The difference does not affect us in practice however. The cast can be
74
- // removed after we support ESM for CLI dependencies and migrate to
75
- // version 3 of node-fetch.
76
- // https://github.com/backstage/backstage/issues/8242
77
- ...signal && { signal }
78
- });
79
- } catch (e) {
80
- throw new Error(`Unable to read ${url}, ${e}`);
81
- }
82
- if (response.status === 304) {
83
- throw new errors.NotModifiedError();
84
- }
85
- if (response.ok) {
86
- return ReadUrlResponseFactory.ReadUrlResponseFactory.fromResponse(response);
87
- }
88
- const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`;
89
- if (response.status === 404) {
90
- throw new errors.NotFoundError(message);
91
- }
92
- throw new Error(message);
93
- }
94
- async readTree(url, options) {
95
- const { filepath } = parseGitUrl__default.default(url);
96
- const lastCommitShortHash = await this.getLastCommitShortHash(url);
97
- if (options?.etag && options.etag === lastCommitShortHash) {
98
- throw new errors.NotModifiedError();
99
- }
100
- const downloadUrl = await integration.getBitbucketDownloadUrl(
101
- url,
102
- this.integration.config
103
- );
104
- const archiveBitbucketResponse = await fetch(
105
- downloadUrl,
106
- integration.getBitbucketRequestOptions(this.integration.config)
107
- );
108
- if (!archiveBitbucketResponse.ok) {
109
- const message = `Failed to read tree from ${url}, ${archiveBitbucketResponse.status} ${archiveBitbucketResponse.statusText}`;
110
- if (archiveBitbucketResponse.status === 404) {
111
- throw new errors.NotFoundError(message);
112
- }
113
- throw new Error(message);
114
- }
115
- return await this.deps.treeResponseFactory.fromTarArchive({
116
- response: archiveBitbucketResponse,
117
- subpath: filepath,
118
- etag: lastCommitShortHash,
119
- filter: options?.filter
120
- });
121
- }
122
- async search(url, options) {
123
- const { filepath } = parseGitUrl__default.default(url);
124
- if (!filepath?.match(/[*?]/)) {
125
- try {
126
- const data = await this.readUrl(url, options);
127
- return {
128
- files: [
129
- {
130
- url,
131
- content: data.buffer,
132
- lastModifiedAt: data.lastModifiedAt
133
- }
134
- ],
135
- etag: data.etag ?? ""
136
- };
137
- } catch (error) {
138
- errors.assertError(error);
139
- if (error.name === "NotFoundError") {
140
- return {
141
- files: [],
142
- etag: ""
143
- };
144
- }
145
- throw error;
146
- }
147
- }
148
- const matcher = new minimatch.Minimatch(filepath);
149
- const treeUrl = lodash.trimEnd(url.replace(filepath, ""), "/");
150
- const tree = await this.readTree(treeUrl, {
151
- etag: options?.etag,
152
- filter: (path) => matcher.match(path)
153
- });
154
- const files = await tree.files();
155
- return {
156
- etag: tree.etag,
157
- files: files.map((file) => ({
158
- url: this.integration.resolveUrl({
159
- url: `/${file.path}`,
160
- base: url
161
- }),
162
- content: file.content,
163
- lastModifiedAt: file.lastModifiedAt
164
- }))
165
- };
166
- }
167
- toString() {
168
- const { host, token, username, appPassword } = this.integration.config;
169
- let authed = Boolean(token);
170
- if (!authed) {
171
- authed = Boolean(username && appPassword);
172
- }
173
- return `bitbucket{host=${host},authed=${authed}}`;
174
- }
175
- async getLastCommitShortHash(url) {
176
- const { resource, name: repoName, owner: project, ref } = parseGitUrl__default.default(url);
177
- let branch = ref;
178
- if (!branch) {
179
- branch = await integration.getBitbucketDefaultBranch(url, this.integration.config);
180
- }
181
- const isHosted = resource === "bitbucket.org";
182
- const commitsApiUrl = isHosted ? `${this.integration.config.apiBaseUrl}/repositories/${project}/${repoName}/commits/${branch}` : `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/commits`;
183
- const commitsResponse = await fetch(
184
- commitsApiUrl,
185
- integration.getBitbucketRequestOptions(this.integration.config)
186
- );
187
- if (!commitsResponse.ok) {
188
- const message = `Failed to retrieve commits from ${commitsApiUrl}, ${commitsResponse.status} ${commitsResponse.statusText}`;
189
- if (commitsResponse.status === 404) {
190
- throw new errors.NotFoundError(message);
191
- }
192
- throw new Error(message);
193
- }
194
- const commits = await commitsResponse.json();
195
- if (isHosted) {
196
- if (commits && commits.values && commits.values.length > 0 && commits.values[0].hash) {
197
- return commits.values[0].hash.substring(0, 12);
198
- }
199
- } else {
200
- if (commits && commits.values && commits.values.length > 0 && commits.values[0].id) {
201
- return commits.values[0].id.substring(0, 12);
202
- }
203
- }
204
- throw new Error(`Failed to read response from ${commitsApiUrl}`);
205
- }
206
- }
207
-
208
- exports.BitbucketUrlReader = BitbucketUrlReader;
209
- //# sourceMappingURL=BitbucketUrlReader.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"BitbucketUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/BitbucketUrlReader.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 {\n assertError,\n NotFoundError,\n NotModifiedError,\n} from '@backstage/errors';\nimport {\n BitbucketIntegration,\n getBitbucketDefaultBranch,\n getBitbucketDownloadUrl,\n getBitbucketFileFetchUrl,\n getBitbucketRequestOptions,\n ScmIntegrations,\n} from '@backstage/integration';\nimport parseGitUrl from 'git-url-parse';\nimport { trimEnd } from 'lodash';\nimport { Minimatch } from 'minimatch';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket v1 and v2 APIs, such\n * as the one exposed by Bitbucket Cloud itself.\n *\n * @public\n * @deprecated in favor of BitbucketCloudUrlReader and BitbucketServerUrlReader\n */\nexport class BitbucketUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, logger, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n return integrations.bitbucket\n .list()\n .filter(\n item =>\n !integrations.bitbucketCloud.byHost(item.config.host) &&\n !integrations.bitbucketServer.byHost(item.config.host),\n )\n .map(integration => {\n const reader = new BitbucketUrlReader(integration, logger, {\n treeResponseFactory,\n });\n const predicate = (url: URL) => url.host === integration.config.host;\n return { reader, predicate };\n });\n };\n\n private readonly integration: BitbucketIntegration;\n private readonly deps: { treeResponseFactory: ReadTreeResponseFactory };\n\n constructor(\n integration: BitbucketIntegration,\n logger: LoggerService,\n deps: { treeResponseFactory: ReadTreeResponseFactory },\n ) {\n this.integration = integration;\n this.deps = deps;\n const { host, token, username, appPassword } = integration.config;\n const replacement =\n host === 'bitbucket.org' ? 'bitbucketCloud' : 'bitbucketServer';\n logger.warn(\n `[Deprecated] Please migrate from \"integrations.bitbucket\" to \"integrations.${replacement}\".`,\n );\n\n if (!token && username && !appPassword) {\n throw new Error(\n `Bitbucket integration for '${host}' has configured a username but is missing a required appPassword.`,\n );\n }\n }\n\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n private getCredentials = async (options?: {\n token?: string;\n }): Promise<{ headers: Record<string, string> }> => {\n if (options?.token) {\n return {\n headers: {\n Authorization: `Bearer ${options.token}`,\n },\n };\n }\n\n return await getBitbucketRequestOptions(this.integration.config);\n };\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const { etag, lastModifiedAfter, signal } = options ?? {};\n const bitbucketUrl = getBitbucketFileFetchUrl(url, this.integration.config);\n const requestOptions = await this.getCredentials(options);\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 getBitbucketDownloadUrl(\n url,\n this.integration.config,\n );\n const archiveBitbucketResponse = await fetch(\n downloadUrl,\n getBitbucketRequestOptions(this.integration.config),\n );\n if (!archiveBitbucketResponse.ok) {\n const message = `Failed to read tree from ${url}, ${archiveBitbucketResponse.status} ${archiveBitbucketResponse.statusText}`;\n if (archiveBitbucketResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n return await this.deps.treeResponseFactory.fromTarArchive({\n response: archiveBitbucketResponse,\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\n // If it's a direct URL we use readUrl instead\n if (!filepath?.match(/[*?]/)) {\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (error) {\n assertError(error);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\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, username, appPassword } = this.integration.config;\n let authed = Boolean(token);\n if (!authed) {\n authed = Boolean(username && appPassword);\n }\n return `bitbucket{host=${host},authed=${authed}}`;\n }\n\n private async getLastCommitShortHash(url: string): Promise<string> {\n const { resource, name: repoName, owner: project, ref } = parseGitUrl(url);\n\n let branch = ref;\n if (!branch) {\n branch = await getBitbucketDefaultBranch(url, this.integration.config);\n }\n\n const isHosted = resource === 'bitbucket.org';\n // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp222\n const commitsApiUrl = isHosted\n ? `${this.integration.config.apiBaseUrl}/repositories/${project}/${repoName}/commits/${branch}`\n : `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/commits`;\n\n const commitsResponse = await fetch(\n commitsApiUrl,\n getBitbucketRequestOptions(this.integration.config),\n );\n if (!commitsResponse.ok) {\n const message = `Failed to retrieve commits from ${commitsApiUrl}, ${commitsResponse.status} ${commitsResponse.statusText}`;\n if (commitsResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n const commits = await commitsResponse.json();\n if (isHosted) {\n if (\n commits &&\n commits.values &&\n commits.values.length > 0 &&\n commits.values[0].hash\n ) {\n return commits.values[0].hash.substring(0, 12);\n }\n } else {\n if (\n commits &&\n commits.values &&\n commits.values.length > 0 &&\n commits.values[0].id\n ) {\n return commits.values[0].id.substring(0, 12);\n }\n }\n\n throw new Error(`Failed to read response from ${commitsApiUrl}`);\n }\n}\n"],"names":["ScmIntegrations","getBitbucketRequestOptions","getBitbucketFileFetchUrl","NotModifiedError","ReadUrlResponseFactory","NotFoundError","parseGitUrl","getBitbucketDownloadUrl","assertError","Minimatch","trimEnd","getBitbucketDefaultBranch"],"mappings":"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"}