add the scheduler definitions to backend-plugin-api/scheduler
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -36,7 +36,7 @@ import { RootConfigService } from '@backstage/backend-plugin-api';
|
||||
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { RootLifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { RootLoggerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api/scheduler';
|
||||
import type { Server } from 'node:http';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactoryOrFunction } from '@backstage/backend-plugin-api';
|
||||
|
||||
@@ -59,7 +59,7 @@ import { resolvePackagePath as resolvePackagePath_2 } from '@backstage/backend-p
|
||||
import { resolveSafeChildPath as resolveSafeChildPath_2 } from '@backstage/backend-plugin-api';
|
||||
import { RootConfigService } from '@backstage/backend-plugin-api';
|
||||
import { Router } from 'express';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api/scheduler';
|
||||
import { SearchOptions } from '@backstage/backend-plugin-api';
|
||||
import { SearchResponse } from '@backstage/backend-plugin-api';
|
||||
import { SearchResponseFile } from '@backstage/backend-plugin-api';
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
## API Report File for "@backstage/backend-plugin-api"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { Config } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { HumanDuration } from '@backstage/types';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
|
||||
// @public
|
||||
export function readTaskScheduleDefinitionFromConfig(
|
||||
config: Config,
|
||||
): TaskScheduleDefinition;
|
||||
|
||||
// @public
|
||||
export interface SchedulerService {
|
||||
createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner;
|
||||
getScheduledTasks(): Promise<TaskDescriptor[]>;
|
||||
scheduleTask(
|
||||
task: TaskScheduleDefinition & TaskInvocationDefinition,
|
||||
): Promise<void>;
|
||||
triggerTask(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type TaskDescriptor = {
|
||||
id: string;
|
||||
scope: 'global' | 'local';
|
||||
settings: {
|
||||
version: number;
|
||||
} & JsonObject;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type TaskFunction =
|
||||
| ((abortSignal: AbortSignal) => void | Promise<void>)
|
||||
| (() => void | Promise<void>);
|
||||
|
||||
// @public
|
||||
export interface TaskInvocationDefinition {
|
||||
fn: TaskFunction;
|
||||
id: string;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TaskRunner {
|
||||
run(task: TaskInvocationDefinition): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TaskScheduleDefinition {
|
||||
frequency:
|
||||
| {
|
||||
cron: string;
|
||||
}
|
||||
| Duration
|
||||
| HumanDuration;
|
||||
initialDelay?: Duration | HumanDuration;
|
||||
scope?: 'global' | 'local';
|
||||
timeout: Duration | HumanDuration;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TaskScheduleDefinitionConfig {
|
||||
frequency:
|
||||
| {
|
||||
cron: string;
|
||||
}
|
||||
| string
|
||||
| HumanDuration;
|
||||
initialDelay?: string | HumanDuration;
|
||||
scope?: 'global' | 'local';
|
||||
timeout: string | HumanDuration;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -15,12 +15,12 @@ import { JsonObject } from '@backstage/types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { Knex } from 'knex';
|
||||
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import { QueryPermissionRequest } from '@backstage/plugin-permission-common';
|
||||
import { QueryPermissionResponse } from '@backstage/plugin-permission-common';
|
||||
import { Readable } from 'stream';
|
||||
import { Request as Request_2 } from 'express';
|
||||
import { Response as Response_2 } from 'express';
|
||||
import { SchedulerService as SchedulerService_2 } from '@backstage/backend-plugin-api/scheduler';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface AuthService {
|
||||
@@ -202,7 +202,7 @@ export namespace coreServices {
|
||||
const rootHttpRouter: ServiceRef<RootHttpRouterService, 'root'>;
|
||||
const rootLifecycle: ServiceRef<RootLifecycleService, 'root'>;
|
||||
const rootLogger: ServiceRef<RootLoggerService, 'root'>;
|
||||
const scheduler: ServiceRef<SchedulerService, 'plugin'>;
|
||||
const scheduler: ServiceRef<SchedulerService_2, 'plugin'>;
|
||||
const tokenManager: ServiceRef<TokenManagerService, 'plugin'>;
|
||||
const urlReader: ServiceRef<UrlReaderService, 'plugin'>;
|
||||
const identity: ServiceRef<IdentityService, 'plugin'>;
|
||||
@@ -536,8 +536,8 @@ export interface RootServiceFactoryConfig<
|
||||
service: ServiceRef<TService, 'root'>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SchedulerService extends PluginTaskScheduler {}
|
||||
// @public @deprecated (undocumented)
|
||||
export type SchedulerService = SchedulerService_2;
|
||||
|
||||
// @public
|
||||
export type SearchOptions = {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./scheduler": "./src/entrypoints/scheduler/index.ts",
|
||||
"./alpha": "./src/alpha.ts",
|
||||
"./testUtils": "./src/testUtils.ts",
|
||||
"./package.json": "./package.json"
|
||||
@@ -28,6 +29,9 @@
|
||||
"types": "src/index.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"scheduler": [
|
||||
"src/entrypoints/scheduler/index.ts"
|
||||
],
|
||||
"alpha": [
|
||||
"src/alpha.ts"
|
||||
],
|
||||
@@ -61,7 +65,8 @@
|
||||
"@backstage/types": "workspace:^",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"knex": "^3.0.0"
|
||||
"knex": "^3.0.0",
|
||||
"luxon": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
|
||||
+7
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import { type SchedulerService as SchedulerService_ } from '@backstage/backend-plugin-api/scheduler';
|
||||
|
||||
/** @public */
|
||||
export interface SchedulerService extends PluginTaskScheduler {}
|
||||
/**
|
||||
* @public
|
||||
* @deprecated import from `@backstage/backend-plugin-api/scheduler` instead
|
||||
*/
|
||||
export type SchedulerService = SchedulerService_;
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig';
|
||||
export type {
|
||||
SchedulerService,
|
||||
TaskDescriptor,
|
||||
TaskFunction,
|
||||
TaskInvocationDefinition,
|
||||
TaskRunner,
|
||||
TaskScheduleDefinition,
|
||||
TaskScheduleDefinitionConfig,
|
||||
} from './types';
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { HumanDuration } from '@backstage/types';
|
||||
import { Duration } from 'luxon';
|
||||
import { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig';
|
||||
|
||||
describe('readTaskScheduleDefinitionFromConfig', () => {
|
||||
it('all valid values', () => {
|
||||
const config = new ConfigReader({
|
||||
frequency: {
|
||||
cron: '0 30 * * * *',
|
||||
},
|
||||
timeout: 'PT3M',
|
||||
initialDelay: {
|
||||
minutes: 20,
|
||||
},
|
||||
scope: 'global',
|
||||
});
|
||||
|
||||
const result = readTaskScheduleDefinitionFromConfig(config);
|
||||
|
||||
expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *');
|
||||
expect(result.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect((result.initialDelay as HumanDuration).minutes).toEqual(20);
|
||||
expect(result.scope).toBe('global');
|
||||
});
|
||||
|
||||
it('all valid required values', () => {
|
||||
const config = new ConfigReader({
|
||||
frequency: {
|
||||
cron: '0 30 * * * *',
|
||||
},
|
||||
timeout: 'PT3M',
|
||||
});
|
||||
|
||||
const result = readTaskScheduleDefinitionFromConfig(config);
|
||||
|
||||
expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *');
|
||||
expect(result.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(result.initialDelay).toBeUndefined();
|
||||
expect(result.scope).toBeUndefined();
|
||||
});
|
||||
|
||||
it('fail without required frequency', () => {
|
||||
const config = new ConfigReader({
|
||||
timeout: 'PT3M',
|
||||
});
|
||||
|
||||
expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow(
|
||||
"Missing required config value at 'frequency'",
|
||||
);
|
||||
});
|
||||
|
||||
it('fail without required timeout', () => {
|
||||
const config = new ConfigReader({
|
||||
frequency: 'PT30M',
|
||||
});
|
||||
|
||||
expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow(
|
||||
"Missing required config value at 'timeout'",
|
||||
);
|
||||
});
|
||||
|
||||
it('invalid frequency key', () => {
|
||||
const config = new ConfigReader({
|
||||
frequency: {
|
||||
invalid: 'value',
|
||||
},
|
||||
timeout: 'PT3M',
|
||||
});
|
||||
|
||||
expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow(
|
||||
"Failed to read duration from config at 'frequency', Error: Needs one or more of 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'",
|
||||
);
|
||||
});
|
||||
|
||||
it('invalid frequency value', () => {
|
||||
const config = new ConfigReader({
|
||||
frequency: {
|
||||
minutes: 'value',
|
||||
},
|
||||
timeout: 'PT3M',
|
||||
});
|
||||
|
||||
expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow(
|
||||
"Failed to read duration from config, Error: Unable to convert config value for key 'frequency.minutes' in 'mock-config' to a number",
|
||||
);
|
||||
});
|
||||
|
||||
it('frequency value with additional invalid prop', () => {
|
||||
const config = new ConfigReader({
|
||||
frequency: {
|
||||
minutes: 20,
|
||||
invalid: 'value',
|
||||
},
|
||||
timeout: 'PT3M',
|
||||
});
|
||||
|
||||
expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow(
|
||||
"Failed to read duration from config at 'frequency', Error: Unknown property 'invalid'; expected one or more of 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'",
|
||||
);
|
||||
});
|
||||
|
||||
it('invalid scope value', () => {
|
||||
const config = new ConfigReader({
|
||||
frequency: {
|
||||
years: 2,
|
||||
},
|
||||
timeout: 'PT3M',
|
||||
scope: 'invalid',
|
||||
});
|
||||
|
||||
expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow(
|
||||
'Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: invalid',
|
||||
);
|
||||
});
|
||||
});
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Config, readDurationFromConfig } from '@backstage/config';
|
||||
import { HumanDuration } from '@backstage/types';
|
||||
import { TaskScheduleDefinition } from './types';
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
function readDuration(config: Config, key: string): Duration | HumanDuration {
|
||||
if (typeof config.get(key) === 'string') {
|
||||
const value = config.getString(key);
|
||||
const duration = Duration.fromISO(value);
|
||||
if (!duration.isValid) {
|
||||
throw new Error(`Invalid duration: ${value}`);
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
return readDurationFromConfig(config, { key });
|
||||
}
|
||||
|
||||
function readCronOrDuration(
|
||||
config: Config,
|
||||
key: string,
|
||||
): { cron: string } | Duration | HumanDuration {
|
||||
const value = config.get(key);
|
||||
if (typeof value === 'object' && (value as { cron?: string }).cron) {
|
||||
return value as { cron: string };
|
||||
}
|
||||
|
||||
return readDuration(config, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a TaskScheduleDefinition from a Config.
|
||||
* Expects the config not to be the root config,
|
||||
* but the config for the definition.
|
||||
*
|
||||
* @param config - config for a TaskScheduleDefinition.
|
||||
* @public
|
||||
*/
|
||||
export function readTaskScheduleDefinitionFromConfig(
|
||||
config: Config,
|
||||
): TaskScheduleDefinition {
|
||||
const frequency = readCronOrDuration(config, 'frequency');
|
||||
const timeout = readDuration(config, 'timeout');
|
||||
|
||||
const initialDelay = config.has('initialDelay')
|
||||
? readDuration(config, 'initialDelay')
|
||||
: undefined;
|
||||
|
||||
const scope = config.getOptionalString('scope');
|
||||
if (scope && !['global', 'local'].includes(scope)) {
|
||||
throw new Error(
|
||||
`Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: ${scope}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
frequency,
|
||||
timeout,
|
||||
initialDelay,
|
||||
scope: scope as 'global' | 'local' | undefined,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HumanDuration, JsonObject } from '@backstage/types';
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
/**
|
||||
* A function that can be called as a scheduled task.
|
||||
*
|
||||
* It may optionally accept an abort signal argument. When the signal triggers,
|
||||
* processing should abort and return as quickly as possible.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TaskFunction =
|
||||
| ((abortSignal: AbortSignal) => void | Promise<void>)
|
||||
| (() => void | Promise<void>);
|
||||
|
||||
/**
|
||||
* A semi-opaque type to describe an actively scheduled task.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TaskDescriptor = {
|
||||
/**
|
||||
* The unique identifier of the task.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The scope of the task.
|
||||
*/
|
||||
scope: 'global' | 'local';
|
||||
/**
|
||||
* The settings that control the task flow. This is a semi-opaque structure
|
||||
* that is mainly there for debugging purposes. Do not make any assumptions
|
||||
* about the contents of this field.
|
||||
*/
|
||||
settings: { version: number } & JsonObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* Options that control the scheduling of a task.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskScheduleDefinition {
|
||||
/**
|
||||
* How often you want the task to run. The system does its best to avoid
|
||||
* overlapping invocations.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This is the best effort value; under some circumstances there can be
|
||||
* deviations. For example, if the task runtime is longer than the frequency
|
||||
* and the timeout has not been given or not been exceeded yet, the next
|
||||
* invocation of this task will be delayed until after the previous one
|
||||
* finishes.
|
||||
*
|
||||
* This is a required field.
|
||||
*/
|
||||
frequency:
|
||||
| {
|
||||
/**
|
||||
* A crontab style string.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Overview:
|
||||
*
|
||||
* ```
|
||||
* ┌────────────── second (optional)
|
||||
* │ ┌──────────── minute
|
||||
* │ │ ┌────────── hour
|
||||
* │ │ │ ┌──────── day of month
|
||||
* │ │ │ │ ┌────── month
|
||||
* │ │ │ │ │ ┌──── day of week
|
||||
* │ │ │ │ │ │
|
||||
* │ │ │ │ │ │
|
||||
* * * * * * *
|
||||
* ```
|
||||
*/
|
||||
cron: string;
|
||||
}
|
||||
| Duration
|
||||
| HumanDuration;
|
||||
|
||||
/**
|
||||
* The maximum amount of time that a single task invocation can take, before
|
||||
* it's considered timed out and gets "released" such that a new invocation
|
||||
* is permitted to take place (possibly, then, on a different worker).
|
||||
*/
|
||||
timeout: Duration | HumanDuration;
|
||||
|
||||
/**
|
||||
* The amount of time that should pass before the first invocation happens.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This can be useful in cold start scenarios to stagger or delay some heavy
|
||||
* compute jobs. If no value is given for this field then the first invocation
|
||||
* will happen as soon as possible according to the cadence.
|
||||
*
|
||||
* NOTE: This is a per-worker delay. If you have a cluster of workers all
|
||||
* collaborating on a task that has its `scope` field set to `'global'`, then
|
||||
* you may still see the task being processed by other long-lived workers,
|
||||
* while any given single worker is in its initial sleep delay time e.g. after
|
||||
* a deployment. Therefore, this parameter is not useful for "globally" pausing
|
||||
* work; its main intended use is for individual machines to get a chance to
|
||||
* reach some equilibrium at startup before triggering heavy batch workloads.
|
||||
*/
|
||||
initialDelay?: Duration | HumanDuration;
|
||||
|
||||
/**
|
||||
* Sets the scope of concurrency control / locking to apply for invocations of
|
||||
* this task.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* When the scope is set to the default value `'global'`, the scheduler will
|
||||
* attempt to ensure that only one worker machine runs the task at a time,
|
||||
* according to the given cadence. This means that as the number of worker
|
||||
* hosts increases, the invocation frequency of this task will not go up.
|
||||
* Instead, the load is spread randomly across hosts. This setting is useful
|
||||
* for tasks that access shared resources, for example catalog ingestion tasks
|
||||
* where you do not want many machines to repeatedly import the same data and
|
||||
* trample over each other.
|
||||
*
|
||||
* When the scope is set to `'local'`, there is no concurrency control across
|
||||
* hosts. Each host runs the task according to the given cadence similarly to
|
||||
* `setInterval`, but the runtime ensures that there are no overlapping runs.
|
||||
*
|
||||
* @defaultValue 'global'
|
||||
*/
|
||||
scope?: 'global' | 'local';
|
||||
}
|
||||
|
||||
/**
|
||||
* Config options for {@link TaskScheduleDefinition}
|
||||
* that control the scheduling of a task.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskScheduleDefinitionConfig {
|
||||
/**
|
||||
* How often you want the task to run. The system does its best to avoid
|
||||
* overlapping invocations.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This is the best effort value; under some circumstances there can be
|
||||
* deviations. For example, if the task runtime is longer than the frequency
|
||||
* and the timeout has not been given or not been exceeded yet, the next
|
||||
* invocation of this task will be delayed until after the previous one
|
||||
* finishes.
|
||||
*
|
||||
* This is a required field.
|
||||
*/
|
||||
frequency:
|
||||
| {
|
||||
/**
|
||||
* A crontab style string.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Overview:
|
||||
*
|
||||
* ```
|
||||
* ┌────────────── second (optional)
|
||||
* │ ┌──────────── minute
|
||||
* │ │ ┌────────── hour
|
||||
* │ │ │ ┌──────── day of month
|
||||
* │ │ │ │ ┌────── month
|
||||
* │ │ │ │ │ ┌──── day of week
|
||||
* │ │ │ │ │ │
|
||||
* │ │ │ │ │ │
|
||||
* * * * * * *
|
||||
* ```
|
||||
*/
|
||||
cron: string;
|
||||
}
|
||||
| string
|
||||
| HumanDuration;
|
||||
|
||||
/**
|
||||
* The maximum amount of time that a single task invocation can take, before
|
||||
* it's considered timed out and gets "released" such that a new invocation
|
||||
* is permitted to take place (possibly, then, on a different worker).
|
||||
*/
|
||||
timeout: string | HumanDuration;
|
||||
|
||||
/**
|
||||
* The amount of time that should pass before the first invocation happens.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This can be useful in cold start scenarios to stagger or delay some heavy
|
||||
* compute jobs. If no value is given for this field then the first invocation
|
||||
* will happen as soon as possible according to the cadence.
|
||||
*
|
||||
* NOTE: This is a per-worker delay. If you have a cluster of workers all
|
||||
* collaborating on a task that has its `scope` field set to `'global'`, then
|
||||
* you may still see the task being processed by other long-lived workers,
|
||||
* while any given single worker is in its initial sleep delay time e.g. after
|
||||
* a deployment. Therefore, this parameter is not useful for "globally" pausing
|
||||
* work; its main intended use is for individual machines to get a chance to
|
||||
* reach some equilibrium at startup before triggering heavy batch workloads.
|
||||
*/
|
||||
initialDelay?: string | HumanDuration;
|
||||
|
||||
/**
|
||||
* Sets the scope of concurrency control / locking to apply for invocations of
|
||||
* this task.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* When the scope is set to the default value `'global'`, the scheduler will
|
||||
* attempt to ensure that only one worker machine runs the task at a time,
|
||||
* according to the given cadence. This means that as the number of worker
|
||||
* hosts increases, the invocation frequency of this task will not go up.
|
||||
* Instead, the load is spread randomly across hosts. This setting is useful
|
||||
* for tasks that access shared resources, for example catalog ingestion tasks
|
||||
* where you do not want many machines to repeatedly import the same data and
|
||||
* trample over each other.
|
||||
*
|
||||
* When the scope is set to `'local'`, there is no concurrency control across
|
||||
* hosts. Each host runs the task according to the given cadence similarly to
|
||||
* `setInterval`, but the runtime ensures that there are no overlapping runs.
|
||||
*
|
||||
* @defaultValue 'global'
|
||||
*/
|
||||
scope?: 'global' | 'local';
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that apply to the invocation of a given task.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskInvocationDefinition {
|
||||
/**
|
||||
* A unique ID (within the scope of the plugin) for the task.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The actual task function to be invoked regularly.
|
||||
*/
|
||||
fn: TaskFunction;
|
||||
|
||||
/**
|
||||
* An abort signal that, when triggered, will stop the recurring execution of
|
||||
* the task.
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* A previously prepared task schedule, ready to be invoked.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskRunner {
|
||||
/**
|
||||
* Takes the schedule and executes an actual task using it.
|
||||
*
|
||||
* @param task - The actual runtime properties of the task
|
||||
*/
|
||||
run(task: TaskInvocationDefinition): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deals with the scheduling of distributed tasks, for a given plugin.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SchedulerService {
|
||||
/**
|
||||
* Manually triggers a task by ID.
|
||||
*
|
||||
* If the task doesn't exist, a NotFoundError is thrown. If the task is
|
||||
* currently running, a ConflictError is thrown.
|
||||
*
|
||||
* @param id - The task ID
|
||||
*/
|
||||
triggerTask(id: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Schedules a task function for recurring runs.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* The `scope` task field controls whether to use coordinated exclusive
|
||||
* invocation across workers, or to just coordinate within the current worker.
|
||||
*
|
||||
* This convenience method performs both the scheduling and invocation in one
|
||||
* go.
|
||||
*
|
||||
* @param task - The task definition
|
||||
*/
|
||||
scheduleTask(
|
||||
task: TaskScheduleDefinition & TaskInvocationDefinition,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Creates a scheduled but dormant recurring task, ready to be launched at a
|
||||
* later time.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This method is useful for pre-creating a schedule in outer code to be
|
||||
* passed into an inner implementation, such that the outer code controls
|
||||
* scheduling while inner code controls implementation.
|
||||
*
|
||||
* @param schedule - The task schedule
|
||||
*/
|
||||
createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner;
|
||||
|
||||
/**
|
||||
* Returns all scheduled tasks registered to this scheduler.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This method is useful for triggering tasks manually using the triggerTask
|
||||
* functionality. Note that the returned tasks contain only tasks that have
|
||||
* been initialized in this instance of the scheduler.
|
||||
*
|
||||
* @returns Scheduled tasks
|
||||
*/
|
||||
getScheduledTasks(): Promise<TaskDescriptor[]>;
|
||||
}
|
||||
@@ -24,3 +24,4 @@ export * from './services';
|
||||
export type { BackendFeature } from './types';
|
||||
export * from './paths';
|
||||
export * from './wiring';
|
||||
export * from './deprecated';
|
||||
|
||||
@@ -165,7 +165,7 @@ export namespace coreServices {
|
||||
* @public
|
||||
*/
|
||||
export const scheduler = createServiceRef<
|
||||
import('./SchedulerService').SchedulerService
|
||||
import('@backstage/backend-plugin-api/scheduler').SchedulerService
|
||||
>({ id: 'core.scheduler' });
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,7 +52,6 @@ export type { PluginMetadataService } from './PluginMetadataService';
|
||||
export type { RootHttpRouterService } from './RootHttpRouterService';
|
||||
export type { RootLifecycleService } from './RootLifecycleService';
|
||||
export type { RootLoggerService } from './RootLoggerService';
|
||||
export type { SchedulerService } from './SchedulerService';
|
||||
export type { TokenManagerService } from './TokenManagerService';
|
||||
export type {
|
||||
ReadTreeOptions,
|
||||
|
||||
@@ -34,7 +34,7 @@ import { RootHttpRouterFactoryOptions } from '@backstage/backend-app-api';
|
||||
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { RootLifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { RootLoggerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api/scheduler';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
import { ServiceRef } from '@backstage/backend-plugin-api';
|
||||
import { TokenManagerService } from '@backstage/backend-plugin-api';
|
||||
|
||||
Reference in New Issue
Block a user