Merge pull request #19372 from backstage/rugvip/noptions

backend-plugin-api: remove plugin and module options
This commit is contained in:
Patrik Oldsberg
2023-08-15 17:40:41 +02:00
committed by GitHub
24 changed files with 604 additions and 474 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Removed the options from the alpha `scaffolderPlugin` export. To extend the scaffolder plugin you instead now use the available extension points, `scaffolderActionsExtensionPoint`, `scaffolderTaskBrokerExtensionPoint`, and `scaffolderTemplatingExtensionPoint`.
+17
View File
@@ -0,0 +1,17 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Deprecated the following type exports, which have been moved to `@backstage/plugin-scaffolder-node` instead:
- `TemplateFilter`
- `TemplateGlobal`
- `TaskStatus`
- `TaskCompletionState`
- `SerializedTask`
- `TaskEventType`
- `SerializedTaskEvent`
- `TaskBrokerDispatchResult`
- `TaskBrokerDispatchOptions`
- `TaskContext`
- `TaskBroker`
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-node': patch
---
Added several new types that were moved from `@backstage/plugin-scaffolder-backend`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-node': patch
---
Added two new alpha extension points, `scaffolderTaskBrokerExtensionPoint` and `scaffolderTemplatingExtensionPoint`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
The `concurrentTasksLimit` option can now be configured via static configuration as well. Setting it to 0 will now also disable the task worker.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-plugin-api': minor
---
**BREAKING**: Removed the ability to define options for plugins and modules. Existing options should be migrated to instead use either static configuration or extension points.
+6 -6
View File
@@ -119,14 +119,14 @@ export namespace coreServices {
}
// @public
export function createBackendModule<TOptions extends [options?: object] = []>(
config: BackendModuleConfig | ((...params: TOptions) => BackendModuleConfig),
): (...params: TOptions) => BackendFeature;
export function createBackendModule(
config: BackendModuleConfig,
): () => BackendFeature;
// @public
export function createBackendPlugin<TOptions extends [options?: object] = []>(
config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig),
): (...params: TOptions) => BackendFeature;
export function createBackendPlugin(
config: BackendPluginConfig,
): () => BackendFeature;
// @public
export function createExtensionPoint<T>(
@@ -33,22 +33,19 @@ describe('createExtensionPoint', () => {
describe('createBackendPlugin', () => {
it('should create a BackendPlugin', () => {
const plugin = createBackendPlugin((_options: { a: string }) => ({
const plugin = createBackendPlugin({
pluginId: 'x',
register(r) {
r.registerInit({ deps: {}, async init() {} });
},
}));
});
expect(plugin).toBeDefined();
expect(plugin({ a: 'a' })).toBeDefined();
expect(plugin({ a: 'a' })).toEqual({
expect(plugin()).toEqual({
$$type: '@backstage/BackendFeature',
version: 'v1',
getRegistrations: expect.any(Function),
});
expect(
(plugin({ a: 'a' }) as InternalBackendFeature).getRegistrations(),
).toEqual([
expect((plugin() as InternalBackendFeature).getRegistrations()).toEqual([
{
type: 'plugin',
pluginId: 'x',
@@ -60,91 +57,27 @@ describe('createBackendPlugin', () => {
},
]);
// @ts-expect-error
expect(plugin()).toBeDefined();
// @ts-expect-error
expect(plugin({ b: 'b' })).toBeDefined();
});
it('should create plugins with optional options', () => {
const plugin = createBackendPlugin((_options?: { a: string }) => ({
pluginId: 'x',
register() {},
}));
expect(plugin).toBeDefined();
expect(plugin({ a: 'a' })).toBeDefined();
expect(plugin()).toBeDefined();
// @ts-expect-error
expect(plugin({ b: 'b' })).toBeDefined();
});
it('should create plugins without options', () => {
const plugin = createBackendPlugin({
pluginId: 'x',
register() {},
});
expect(plugin).toBeDefined();
// @ts-expect-error
expect(plugin({ a: 'a' })).toBeDefined();
// @ts-expect-error
expect(plugin({})).toBeDefined();
});
it('should create a BackendPlugin with options as interface', () => {
interface TestOptions {
a: string;
}
const plugin = createBackendPlugin((_options: TestOptions) => ({
pluginId: 'x',
register() {},
}));
expect(plugin).toBeDefined();
expect(plugin({ a: 'a' })).toBeDefined();
expect(plugin({ a: 'a' })).toEqual({
$$type: '@backstage/BackendFeature',
version: 'v1',
getRegistrations: expect.any(Function),
});
// @ts-expect-error
expect(plugin()).toBeDefined();
// @ts-expect-error
expect(plugin({ b: 'b' })).toBeDefined();
});
it('should create plugins with optional options as interface', () => {
interface TestOptions {
a: string;
}
const plugin = createBackendPlugin((_options?: TestOptions) => ({
pluginId: 'x',
register() {},
}));
expect(plugin).toBeDefined();
expect(plugin({ a: 'a' })).toBeDefined();
expect(plugin()).toBeDefined();
// @ts-expect-error
expect(plugin({ b: 'b' })).toBeDefined();
});
});
describe('createBackendModule', () => {
it('should create a BackendModule', () => {
const mod = createBackendModule((_options: { a: string }) => ({
const mod = createBackendModule({
pluginId: 'x',
moduleId: 'y',
register(r) {
r.registerInit({ deps: {}, async init() {} });
},
}));
});
expect(mod).toBeDefined();
expect(mod({ a: 'a' })).toBeDefined();
expect(mod({ a: 'a' })).toEqual({
expect(mod()).toBeDefined();
expect(mod()).toEqual({
$$type: '@backstage/BackendFeature',
version: 'v1',
getRegistrations: expect.any(Function),
});
expect(
(mod({ a: 'a' }) as InternalBackendFeature).getRegistrations(),
).toEqual([
expect((mod() as InternalBackendFeature).getRegistrations()).toEqual([
{
type: 'module',
pluginId: 'x',
@@ -157,72 +90,6 @@ describe('createBackendModule', () => {
},
]);
// @ts-expect-error
expect(mod()).toBeDefined();
// @ts-expect-error
expect(mod({ b: 'b' })).toBeDefined();
});
it('should create modules with optional options', () => {
const mod = createBackendModule((_options?: { a: string }) => ({
pluginId: 'x',
moduleId: 'y',
register() {},
}));
expect(mod).toBeDefined();
expect(mod({ a: 'a' })).toBeDefined();
expect(mod()).toBeDefined();
// @ts-expect-error
expect(mod({ b: 'b' })).toBeDefined();
});
it('should create modules without options', () => {
const mod = createBackendModule({
pluginId: 'x',
moduleId: 'y',
register() {},
});
expect(mod).toBeDefined();
// @ts-expect-error
expect(mod({ a: 'a' })).toBeDefined();
// @ts-expect-error
expect(mod({})).toBeDefined();
});
it('should create a BackendModule as interface', () => {
interface TestOptions {
a: string;
}
const mod = createBackendModule((_options: TestOptions) => ({
pluginId: 'x',
moduleId: 'y',
register() {},
}));
expect(mod).toBeDefined();
expect(mod({ a: 'a' })).toBeDefined();
expect(mod({ a: 'a' })).toEqual({
$$type: '@backstage/BackendFeature',
version: 'v1',
getRegistrations: expect.any(Function),
});
// @ts-expect-error
expect(mod()).toBeDefined();
// @ts-expect-error
expect(mod({ b: 'b' })).toBeDefined();
});
it('should create modules with optional options as interface', () => {
interface TestOptions {
a: string;
}
const mod = createBackendModule((_options?: TestOptions) => ({
pluginId: 'x',
moduleId: 'y',
register() {},
}));
expect(mod).toBeDefined();
expect(mod({ a: 'a' })).toBeDefined();
expect(mod()).toBeDefined();
// @ts-expect-error
expect(mod({ b: 'b' })).toBeDefined();
});
});
@@ -85,14 +85,10 @@ export interface BackendPluginConfig {
* @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins}
* @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
*/
export function createBackendPlugin<TOptions extends [options?: object] = []>(
config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig),
): (...params: TOptions) => BackendFeature {
const configCallback = typeof config === 'function' ? config : () => config;
const factory: BackendFeatureFactory<TOptions> = (...options) => {
const c = configCallback(...options);
export function createBackendPlugin(
config: BackendPluginConfig,
): () => BackendFeature {
const factory: BackendFeatureFactory = () => {
let registrations: InternalBackendPluginRegistration[];
return {
@@ -107,7 +103,7 @@ export function createBackendPlugin<TOptions extends [options?: object] = []>(
let init: InternalBackendPluginRegistration['init'] | undefined =
undefined;
c.register({
config.register({
registerExtensionPoint(ext, impl) {
if (init) {
throw new Error(
@@ -129,14 +125,14 @@ export function createBackendPlugin<TOptions extends [options?: object] = []>(
if (!init) {
throw new Error(
`registerInit was not called by register in ${c.pluginId}`,
`registerInit was not called by register in ${config.pluginId}`,
);
}
registrations = [
{
type: 'plugin',
pluginId: c.pluginId,
pluginId: config.pluginId,
extensionPoints,
init,
},
@@ -179,13 +175,10 @@ export interface BackendModuleConfig {
* @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules}
* @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
*/
export function createBackendModule<TOptions extends [options?: object] = []>(
config: BackendModuleConfig | ((...params: TOptions) => BackendModuleConfig),
): (...params: TOptions) => BackendFeature {
const configCallback = typeof config === 'function' ? config : () => config;
const factory: BackendFeatureFactory<TOptions> = (...options: TOptions) => {
const c = configCallback(...options);
export function createBackendModule(
config: BackendModuleConfig,
): () => BackendFeature {
const factory: BackendFeatureFactory = () => {
let registrations: InternalBackendModuleRegistration[];
return {
@@ -200,7 +193,7 @@ export function createBackendModule<TOptions extends [options?: object] = []>(
let init: InternalBackendModuleRegistration['init'] | undefined =
undefined;
c.register({
config.register({
registerExtensionPoint(ext, impl) {
if (init) {
throw new Error(
@@ -222,15 +215,15 @@ export function createBackendModule<TOptions extends [options?: object] = []>(
if (!init) {
throw new Error(
`registerInit was not called by register in ${c.moduleId} module for ${c.pluginId}`,
`registerInit was not called by register in ${config.moduleId} module for ${config.pluginId}`,
);
}
registrations = [
{
type: 'module',
pluginId: c.pluginId,
moduleId: c.moduleId,
pluginId: config.pluginId,
moduleId: config.moduleId,
extensionPoints,
init,
},
@@ -72,10 +72,8 @@ export interface BackendModuleRegistrationPoints {
}
/** @internal */
export interface BackendFeatureFactory<
TOptions extends [options?: object] = [],
> {
(...options: TOptions): BackendFeature;
export interface BackendFeatureFactory {
(): BackendFeature;
$$type: '@backstage/BackendFeatureFactory';
}
+1 -16
View File
@@ -12,11 +12,7 @@ import { PermissionCriteria } from '@backstage/plugin-permission-common';
import { PermissionRule } from '@backstage/plugin-permission-node';
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
import { ResourcePermission } from '@backstage/plugin-permission-common';
import { TaskBroker } from '@backstage/plugin-scaffolder-backend';
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common';
import { TemplateFilter } from '@backstage/plugin-scaffolder-backend';
import { TemplateGlobal } from '@backstage/plugin-scaffolder-backend';
import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common';
// @alpha
@@ -90,18 +86,7 @@ export const scaffolderActionConditions: Conditions<{
}>;
// @alpha
export const scaffolderPlugin: (
options?: ScaffolderPluginOptions | undefined,
) => BackendFeature;
// @alpha
export type ScaffolderPluginOptions = {
actions?: TemplateAction<any, any>[];
taskWorkers?: number;
taskBroker?: TaskBroker;
additionalTemplateFilters?: Record<string, TemplateFilter>;
additionalTemplateGlobals?: Record<string, TemplateGlobal>;
};
export const scaffolderPlugin: () => BackendFeature;
// @alpha
export const scaffolderTemplateConditions: Conditions<{
+33 -91
View File
@@ -19,11 +19,9 @@ import { GithubCredentialsProvider } from '@backstage/integration';
import { HumanDuration } from '@backstage/types';
import { IdentityApi } from '@backstage/plugin-auth-node';
import { JsonObject } from '@backstage/types';
import { JsonValue } from '@backstage/types';
import { Knex } from 'knex';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import { Logger } from 'winston';
import { Observable } from '@backstage/types';
import { Octokit } from 'octokit';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
import { PermissionRule } from '@backstage/plugin-permission-node';
@@ -35,12 +33,23 @@ import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-
import { Schema } from 'jsonschema';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { ScmIntegrations } from '@backstage/integration';
import { SerializedTask as SerializedTask_2 } from '@backstage/plugin-scaffolder-node';
import { SerializedTaskEvent as SerializedTaskEvent_2 } from '@backstage/plugin-scaffolder-node';
import { TaskBroker as TaskBroker_2 } from '@backstage/plugin-scaffolder-node';
import { TaskBrokerDispatchOptions as TaskBrokerDispatchOptions_2 } from '@backstage/plugin-scaffolder-node';
import { TaskBrokerDispatchResult as TaskBrokerDispatchResult_2 } from '@backstage/plugin-scaffolder-node';
import { TaskCompletionState as TaskCompletionState_2 } from '@backstage/plugin-scaffolder-node';
import { TaskContext as TaskContext_2 } from '@backstage/plugin-scaffolder-node';
import { TaskEventType as TaskEventType_2 } from '@backstage/plugin-scaffolder-node';
import { TaskSecrets as TaskSecrets_2 } from '@backstage/plugin-scaffolder-node';
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common';
import { TaskStatus as TaskStatus_2 } from '@backstage/plugin-scaffolder-node';
import { TemplateAction as TemplateAction_2 } from '@backstage/plugin-scaffolder-node';
import { TemplateActionOptions } from '@backstage/plugin-scaffolder-node';
import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common';
import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node';
import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node';
import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common';
import { UrlReader } from '@backstage/backend-common';
import { ZodType } from 'zod';
@@ -886,89 +895,29 @@ export class ScaffolderEntitiesProcessor implements CatalogProcessor {
validateEntityKind(entity: Entity): Promise<boolean>;
}
// @public
export type SerializedTask = {
id: string;
spec: TaskSpec;
status: TaskStatus;
createdAt: string;
lastHeartbeatAt?: string;
createdBy?: string;
secrets?: TaskSecrets_2;
};
// @public @deprecated
export type SerializedTask = SerializedTask_2;
// @public
export type SerializedTaskEvent = {
id: number;
taskId: string;
body: JsonObject;
type: TaskEventType;
createdAt: string;
};
// @public @deprecated
export type SerializedTaskEvent = SerializedTaskEvent_2;
// @public
export interface TaskBroker {
// (undocumented)
cancel?(taskId: string): Promise<void>;
// (undocumented)
claim(): Promise<TaskContext>;
// (undocumented)
dispatch(
options: TaskBrokerDispatchOptions,
): Promise<TaskBrokerDispatchResult>;
// (undocumented)
event$(options: { taskId: string; after: number | undefined }): Observable<{
events: SerializedTaskEvent[];
}>;
// (undocumented)
get(taskId: string): Promise<SerializedTask>;
// (undocumented)
list?(options?: { createdBy?: string }): Promise<{
tasks: SerializedTask[];
}>;
// (undocumented)
vacuumTasks(options: { timeoutS: number }): Promise<void>;
}
// @public @deprecated
export type TaskBroker = TaskBroker_2;
// @public
export type TaskBrokerDispatchOptions = {
spec: TaskSpec;
secrets?: TaskSecrets_2;
createdBy?: string;
};
// @public @deprecated
export type TaskBrokerDispatchOptions = TaskBrokerDispatchOptions_2;
// @public
export type TaskBrokerDispatchResult = {
taskId: string;
};
// @public @deprecated
export type TaskBrokerDispatchResult = TaskBrokerDispatchResult_2;
// @public
export type TaskCompletionState = 'failed' | 'completed';
// @public @deprecated
export type TaskCompletionState = TaskCompletionState_2;
// @public
export interface TaskContext {
// (undocumented)
cancelSignal: AbortSignal;
// (undocumented)
complete(result: TaskCompletionState, metadata?: JsonObject): Promise<void>;
// (undocumented)
createdBy?: string;
// (undocumented)
done: boolean;
// (undocumented)
emitLog(message: string, logMetadata?: JsonObject): Promise<void>;
// (undocumented)
getWorkspaceName(): Promise<string>;
// (undocumented)
isDryRun?: boolean;
// (undocumented)
secrets?: TaskSecrets_2;
// (undocumented)
spec: TaskSpec;
}
// @public @deprecated
export type TaskContext = TaskContext_2;
// @public
export type TaskEventType = 'completion' | 'log' | 'cancelled';
// @public @deprecated
export type TaskEventType = TaskEventType_2;
// @public
export class TaskManager implements TaskContext {
@@ -1000,13 +949,8 @@ export class TaskManager implements TaskContext {
// @public @deprecated (undocumented)
export type TaskSecrets = TaskSecrets_2;
// @public
export type TaskStatus =
| 'cancelled'
| 'completed'
| 'failed'
| 'open'
| 'processing';
// @public @deprecated
export type TaskStatus = TaskStatus_2;
// @public
export interface TaskStore {
@@ -1103,13 +1047,11 @@ export class TemplateActionRegistry {
register(action: TemplateAction_2): void;
}
// @public (undocumented)
export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined;
// @public @deprecated (undocumented)
export type TemplateFilter = TemplateFilter_2;
// @public (undocumented)
export type TemplateGlobal =
| ((...args: JsonValue[]) => JsonValue | undefined)
| JsonValue;
// @public @deprecated (undocumented)
export type TemplateGlobal = TemplateGlobal_2;
// @public (undocumented)
export type TemplatePermissionRuleInput<
+9
View File
@@ -28,5 +28,14 @@ export interface Config {
* The commit message used when new components are created.
*/
defaultCommitMessage?: string;
/**
* Sets the number of concurrent tasks that can be run at any given time on the TaskWorker.
*
* Defaults to 10.
*
* Set to 0 to disable task workers altogether.
*/
concurrentTasksLimit?: number;
};
}
@@ -23,120 +23,104 @@ import { ScmIntegrations } from '@backstage/integration';
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
import {
scaffolderActionsExtensionPoint,
ScaffolderActionsExtensionPoint,
scaffolderTaskBrokerExtensionPoint,
scaffolderTemplatingExtensionPoint,
TaskBroker,
TemplateAction,
} from '@backstage/plugin-scaffolder-node';
import {
TemplateFilter,
TemplateGlobal,
TaskBroker,
} from '@backstage/plugin-scaffolder-backend';
} from '@backstage/plugin-scaffolder-node';
import { createBuiltinActions } from './scaffolder';
import { createRouter } from './service/router';
/**
* Catalog plugin options
* Scaffolder plugin
*
* @alpha
*/
export type ScaffolderPluginOptions = {
actions?: TemplateAction<any, any>[];
taskWorkers?: number;
taskBroker?: TaskBroker;
additionalTemplateFilters?: Record<string, TemplateFilter>;
additionalTemplateGlobals?: Record<string, TemplateGlobal>;
};
export const scaffolderPlugin = createBackendPlugin({
pluginId: 'scaffolder',
register(env) {
const addedActions = new Array<TemplateAction<any, any>>();
env.registerExtensionPoint(scaffolderActionsExtensionPoint, {
addActions(...newActions: TemplateAction<any>[]) {
addedActions.push(...newActions);
},
});
class ScaffolderActionsExtensionPointImpl
implements ScaffolderActionsExtensionPoint
{
#actions = new Array<TemplateAction<any, any>>();
let taskBroker: TaskBroker | undefined;
env.registerExtensionPoint(scaffolderTaskBrokerExtensionPoint, {
setTaskBroker(newTaskBroker) {
if (taskBroker) {
throw new Error('Task broker may only be set once');
}
taskBroker = newTaskBroker;
},
});
addActions(...actions: TemplateAction<any>[]): void {
this.#actions.push(...actions);
}
const additionalTemplateFilters: Record<string, TemplateFilter> = {};
const additionalTemplateGlobals: Record<string, TemplateGlobal> = {};
env.registerExtensionPoint(scaffolderTemplatingExtensionPoint, {
addTemplateFilters(newFilters) {
Object.assign(additionalTemplateFilters, newFilters);
},
addTemplateGlobals(newGlobals) {
Object.assign(additionalTemplateGlobals, newGlobals);
},
});
get actions() {
return this.#actions;
}
}
env.registerInit({
deps: {
logger: coreServices.logger,
config: coreServices.rootConfig,
reader: coreServices.urlReader,
permissions: coreServices.permissions,
database: coreServices.database,
httpRouter: coreServices.httpRouter,
catalogClient: catalogServiceRef,
},
async init({
logger,
config,
reader,
database,
httpRouter,
catalogClient,
permissions,
}) {
const log = loggerToWinstonLogger(logger);
/**
* Catalog plugin
*
* @alpha
*/
export const scaffolderPlugin = createBackendPlugin(
(options?: ScaffolderPluginOptions) => ({
pluginId: 'scaffolder',
register(env) {
const actionsExtensions = new ScaffolderActionsExtensionPointImpl();
env.registerExtensionPoint(
scaffolderActionsExtensionPoint,
actionsExtensions,
);
env.registerInit({
deps: {
logger: coreServices.logger,
config: coreServices.rootConfig,
reader: coreServices.urlReader,
permissions: coreServices.permissions,
database: coreServices.database,
httpRouter: coreServices.httpRouter,
catalogClient: catalogServiceRef,
},
async init({
logger,
config,
reader,
database,
httpRouter,
catalogClient,
permissions,
}) {
const {
additionalTemplateFilters,
taskBroker,
taskWorkers,
additionalTemplateGlobals,
} = options ?? {};
const log = loggerToWinstonLogger(logger);
const actions = options?.actions || [
...actionsExtensions.actions,
...createBuiltinActions({
integrations: ScmIntegrations.fromConfig(config),
catalogClient,
reader,
config,
additionalTemplateFilters,
additionalTemplateGlobals,
}),
];
const actionIds = actions.map(action => action.id).join(', ');
log.info(
`Starting scaffolder with the following actions enabled ${actionIds}`,
);
const router = await createRouter({
logger: log,
config,
database,
const actions = [
...addedActions,
...createBuiltinActions({
integrations: ScmIntegrations.fromConfig(config),
catalogClient,
reader,
actions,
taskBroker,
taskWorkers,
config,
additionalTemplateFilters,
additionalTemplateGlobals,
permissions,
});
httpRouter.use(router);
},
});
},
}),
);
}),
];
const actionIds = actions.map(action => action.id).join(', ');
log.info(
`Starting scaffolder with the following actions enabled ${actionIds}`,
);
const router = await createRouter({
logger: log,
config,
database,
catalogClient,
reader,
actions,
taskBroker,
additionalTemplateFilters,
additionalTemplateGlobals,
permissions,
});
httpRouter.use(router);
},
});
},
});
-1
View File
@@ -17,4 +17,3 @@
export * from './modules';
export * from './service';
export { scaffolderPlugin } from './ScaffolderPlugin';
export type { ScaffolderPluginOptions } from './ScaffolderPlugin';
@@ -16,6 +16,10 @@
import { Isolate } from 'isolated-vm';
import { resolvePackagePath } from '@backstage/backend-common';
import {
TemplateFilter as _TemplateFilter,
TemplateGlobal as _TemplateGlobal,
} from '@backstage/plugin-scaffolder-node';
import fs from 'fs-extra';
import { JsonValue } from '@backstage/types';
@@ -86,13 +90,17 @@ const { render, renderCompat } = (() => {
})();
`;
/** @public */
export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined;
/**
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export type TemplateFilter = _TemplateFilter;
/** @public */
export type TemplateGlobal =
| ((...args: JsonValue[]) => JsonValue | undefined)
| JsonValue;
/**
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export type TemplateGlobal = _TemplateGlobal;
export interface SecureTemplaterOptions {
/* Enables jinja compatibility and the "jsonify" filter */
@@ -14,131 +14,94 @@
* limitations under the License.
*/
import { JsonValue, JsonObject, Observable } from '@backstage/types';
import { JsonValue, JsonObject } from '@backstage/types';
import { TaskSpec, TaskStep } from '@backstage/plugin-scaffolder-common';
import { TaskSecrets } from '@backstage/plugin-scaffolder-node';
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
import {
TemplateAction,
TaskStatus as _TaskStatus,
TaskCompletionState as _TaskCompletionState,
SerializedTask as _SerializedTask,
TaskEventType as _TaskEventType,
SerializedTaskEvent as _SerializedTaskEvent,
TaskBrokerDispatchResult as _TaskBrokerDispatchResult,
TaskBrokerDispatchOptions as _TaskBrokerDispatchOptions,
TaskContext as _TaskContext,
TaskBroker as _TaskBroker,
} from '@backstage/plugin-scaffolder-node';
/**
* The status of each step of the Task
*
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export type TaskStatus =
| 'cancelled'
| 'completed'
| 'failed'
| 'open'
| 'processing';
export type TaskStatus = _TaskStatus;
/**
* The state of a completed task.
*
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export type TaskCompletionState = 'failed' | 'completed';
export type TaskCompletionState = _TaskCompletionState;
/**
* SerializedTask
*
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export type SerializedTask = {
id: string;
spec: TaskSpec;
status: TaskStatus;
createdAt: string;
lastHeartbeatAt?: string;
createdBy?: string;
secrets?: TaskSecrets;
};
export type SerializedTask = _SerializedTask;
/**
* TaskEventType
*
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export type TaskEventType = 'completion' | 'log' | 'cancelled';
export type TaskEventType = _TaskEventType;
/**
* SerializedTaskEvent
*
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export type SerializedTaskEvent = {
id: number;
taskId: string;
body: JsonObject;
type: TaskEventType;
createdAt: string;
};
export type SerializedTaskEvent = _SerializedTaskEvent;
/**
* The result of {@link TaskBroker.dispatch}
* The result of `TaskBroker.dispatch`.
*
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export type TaskBrokerDispatchResult = {
taskId: string;
};
export type TaskBrokerDispatchResult = _TaskBrokerDispatchResult;
/**
* The options passed to {@link TaskBroker.dispatch}
* The options passed to `TaskBroker.dispatch`.
* Currently a spec and optional secrets
*
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export type TaskBrokerDispatchOptions = {
spec: TaskSpec;
secrets?: TaskSecrets;
createdBy?: string;
};
export type TaskBrokerDispatchOptions = _TaskBrokerDispatchOptions;
/**
* Task
*
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export interface TaskContext {
cancelSignal: AbortSignal;
spec: TaskSpec;
secrets?: TaskSecrets;
createdBy?: string;
done: boolean;
isDryRun?: boolean;
complete(result: TaskCompletionState, metadata?: JsonObject): Promise<void>;
emitLog(message: string, logMetadata?: JsonObject): Promise<void>;
getWorkspaceName(): Promise<string>;
}
export type TaskContext = _TaskContext;
/**
* TaskBroker
*
* @public
* @deprecated Import from `@backstage/plugin-scaffolder-node` instead.
*/
export interface TaskBroker {
cancel?(taskId: string): Promise<void>;
claim(): Promise<TaskContext>;
dispatch(
options: TaskBrokerDispatchOptions,
): Promise<TaskBrokerDispatchResult>;
vacuumTasks(options: { timeoutS: number }): Promise<void>;
event$(options: {
taskId: string;
after: number | undefined;
}): Observable<{ events: SerializedTaskEvent[] }>;
get(taskId: string): Promise<SerializedTask>;
list?(options?: { createdBy?: string }): Promise<{ tasks: SerializedTask[] }>;
}
export type TaskBroker = _TaskBroker;
/**
* TaskStoreEmitOptions
@@ -231,13 +231,15 @@ export async function createRouter(
catalogClient,
actions,
taskWorkers,
concurrentTasksLimit,
scheduler,
additionalTemplateFilters,
additionalTemplateGlobals,
permissions,
permissionRules,
} = options;
const concurrentTasksLimit =
options.concurrentTasksLimit ??
options.config.getOptionalNumber('scaffolder.concurrentTasksLimit');
const logger = parentLogger.child({ plugin: 'scaffolder' });
@@ -275,19 +277,21 @@ export async function createRouter(
const actionRegistry = new TemplateActionRegistry();
const workers = [];
for (let i = 0; i < (taskWorkers || 1); i++) {
const worker = await TaskWorker.create({
taskBroker,
actionRegistry,
integrations,
logger,
workingDirectory,
additionalTemplateFilters,
additionalTemplateGlobals,
concurrentTasksLimit,
permissions,
});
workers.push(worker);
if (concurrentTasksLimit !== 0) {
for (let i = 0; i < (taskWorkers || 1); i++) {
const worker = await TaskWorker.create({
taskBroker,
actionRegistry,
integrations,
logger,
workingDirectory,
additionalTemplateFilters,
additionalTemplateGlobals,
concurrentTasksLimit,
permissions,
});
workers.push(worker);
}
}
const actionsToRegister = Array.isArray(actions)
+128 -1
View File
@@ -7,10 +7,17 @@
import { ExtensionPoint } from '@backstage/backend-plugin-api';
import { JsonObject } from '@backstage/types';
import { JsonValue } from '@backstage/types';
import { Logger } from 'winston';
import { Observable } from '@backstage/types';
import { Schema } from 'jsonschema';
import { ScmIntegrations } from '@backstage/integration';
import { SpawnOptionsWithoutStdio } from 'child_process';
import { TaskBroker as TaskBroker_2 } from '@backstage/plugin-scaffolder-node';
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
import { TemplateAction as TemplateAction_2 } from '@backstage/plugin-scaffolder-node';
import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node';
import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node';
import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
import { UrlReader } from '@backstage/backend-common';
import { UserEntity } from '@backstage/catalog-model';
@@ -105,17 +112,129 @@ export function fetchFile(options: {
// @alpha
export interface ScaffolderActionsExtensionPoint {
// (undocumented)
addActions(...actions: TemplateAction<any, any>[]): void;
addActions(...actions: TemplateAction_2<any, any>[]): void;
}
// @alpha
export const scaffolderActionsExtensionPoint: ExtensionPoint<ScaffolderActionsExtensionPoint>;
// @alpha
export interface ScaffolderTaskBrokerExtensionPoint {
// (undocumented)
setTaskBroker(taskBroker: TaskBroker_2): void;
}
// @alpha
export const scaffolderTaskBrokerExtensionPoint: ExtensionPoint<ScaffolderTaskBrokerExtensionPoint>;
// @alpha
export interface ScaffolderTemplatingExtensionPoint {
// (undocumented)
addTemplateFilters(filters: Record<string, TemplateFilter_2>): void;
// (undocumented)
addTemplateGlobals(filters: Record<string, TemplateGlobal_2>): void;
}
// @alpha
export const scaffolderTemplatingExtensionPoint: ExtensionPoint<ScaffolderTemplatingExtensionPoint>;
// @public
export type SerializedTask = {
id: string;
spec: TaskSpec;
status: TaskStatus;
createdAt: string;
lastHeartbeatAt?: string;
createdBy?: string;
secrets?: TaskSecrets;
};
// @public
export type SerializedTaskEvent = {
id: number;
taskId: string;
body: JsonObject;
type: TaskEventType;
createdAt: string;
};
// @public
export interface TaskBroker {
// (undocumented)
cancel?(taskId: string): Promise<void>;
// (undocumented)
claim(): Promise<TaskContext>;
// (undocumented)
dispatch(
options: TaskBrokerDispatchOptions,
): Promise<TaskBrokerDispatchResult>;
// (undocumented)
event$(options: { taskId: string; after: number | undefined }): Observable<{
events: SerializedTaskEvent[];
}>;
// (undocumented)
get(taskId: string): Promise<SerializedTask>;
// (undocumented)
list?(options?: { createdBy?: string }): Promise<{
tasks: SerializedTask[];
}>;
// (undocumented)
vacuumTasks(options: { timeoutS: number }): Promise<void>;
}
// @public
export type TaskBrokerDispatchOptions = {
spec: TaskSpec;
secrets?: TaskSecrets;
createdBy?: string;
};
// @public
export type TaskBrokerDispatchResult = {
taskId: string;
};
// @public
export type TaskCompletionState = 'failed' | 'completed';
// @public
export interface TaskContext {
// (undocumented)
cancelSignal: AbortSignal;
// (undocumented)
complete(result: TaskCompletionState, metadata?: JsonObject): Promise<void>;
// (undocumented)
createdBy?: string;
// (undocumented)
done: boolean;
// (undocumented)
emitLog(message: string, logMetadata?: JsonObject): Promise<void>;
// (undocumented)
getWorkspaceName(): Promise<string>;
// (undocumented)
isDryRun?: boolean;
// (undocumented)
secrets?: TaskSecrets;
// (undocumented)
spec: TaskSpec;
}
// @public
export type TaskEventType = 'completion' | 'log' | 'cancelled';
// @public
export type TaskSecrets = Record<string, string> & {
backstageToken?: string;
};
// @public
export type TaskStatus =
| 'cancelled'
| 'completed'
| 'failed'
| 'open'
| 'processing';
// @public (undocumented)
export type TemplateAction<
TActionInput extends JsonObject = JsonObject,
@@ -158,4 +277,12 @@ export type TemplateExample = {
description: string;
example: string;
};
// @public (undocumented)
export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined;
// @public (undocumented)
export type TemplateGlobal =
| ((...args: JsonValue[]) => JsonValue | undefined)
| JsonValue;
```
+45 -1
View File
@@ -15,7 +15,12 @@
*/
import { createExtensionPoint } from '@backstage/backend-plugin-api';
import { TemplateAction } from './actions';
import {
TemplateAction,
TemplateFilter,
TemplateGlobal,
TaskBroker,
} from '@backstage/plugin-scaffolder-node';
/**
* Extension point for managing scaffolder actions.
@@ -35,3 +40,42 @@ export const scaffolderActionsExtensionPoint =
createExtensionPoint<ScaffolderActionsExtensionPoint>({
id: 'scaffolder.actions',
});
/**
* Extension point for replacing the scaffolder task broker.
*
* @alpha
*/
export interface ScaffolderTaskBrokerExtensionPoint {
setTaskBroker(taskBroker: TaskBroker): void;
}
/**
* Extension point for replacing the scaffolder task broker.
*
* @alpha
*/
export const scaffolderTaskBrokerExtensionPoint =
createExtensionPoint<ScaffolderTaskBrokerExtensionPoint>({
id: 'scaffolder.taskBroker',
});
/**
* Extension point for adding template filters and globals.
*
* @alpha
*/
export interface ScaffolderTemplatingExtensionPoint {
addTemplateFilters(filters: Record<string, TemplateFilter>): void;
addTemplateGlobals(filters: Record<string, TemplateGlobal>): void;
}
/**
* Extension point for adding template filters and globals.
*
* @alpha
*/
export const scaffolderTemplatingExtensionPoint =
createExtensionPoint<ScaffolderTemplatingExtensionPoint>({
id: 'scaffolder.templating',
});
+5
View File
@@ -22,7 +22,12 @@
export * from './actions';
export * from './tasks';
export type { TemplateFilter, TemplateGlobal } from './types';
export {
scaffolderActionsExtensionPoint,
type ScaffolderActionsExtensionPoint,
scaffolderTaskBrokerExtensionPoint,
type ScaffolderTaskBrokerExtensionPoint,
scaffolderTemplatingExtensionPoint,
type ScaffolderTemplatingExtensionPoint,
} from './extensions';
+12 -1
View File
@@ -14,4 +14,15 @@
* limitations under the License.
*/
export { type TaskSecrets } from './types';
export type {
TaskSecrets,
SerializedTask,
SerializedTaskEvent,
TaskBroker,
TaskBrokerDispatchOptions,
TaskBrokerDispatchResult,
TaskCompletionState,
TaskContext,
TaskEventType,
TaskStatus,
} from './types';
+124
View File
@@ -14,6 +14,9 @@
* limitations under the License.
*/
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
import { JsonObject, Observable } from '@backstage/types';
/**
* TaskSecrets
*
@@ -22,3 +25,124 @@
export type TaskSecrets = Record<string, string> & {
backstageToken?: string;
};
/**
* The status of each step of the Task
*
* @public
*/
export type TaskStatus =
| 'cancelled'
| 'completed'
| 'failed'
| 'open'
| 'processing';
/**
* The state of a completed task.
*
* @public
*/
export type TaskCompletionState = 'failed' | 'completed';
/**
* SerializedTask
*
* @public
*/
export type SerializedTask = {
id: string;
spec: TaskSpec;
status: TaskStatus;
createdAt: string;
lastHeartbeatAt?: string;
createdBy?: string;
secrets?: TaskSecrets;
};
/**
* TaskEventType
*
* @public
*/
export type TaskEventType = 'completion' | 'log' | 'cancelled';
/**
* SerializedTaskEvent
*
* @public
*/
export type SerializedTaskEvent = {
id: number;
taskId: string;
body: JsonObject;
type: TaskEventType;
createdAt: string;
};
/**
* The result of {@link TaskBroker.dispatch}
*
* @public
*/
export type TaskBrokerDispatchResult = {
taskId: string;
};
/**
* The options passed to {@link TaskBroker.dispatch}
* Currently a spec and optional secrets
*
* @public
*/
export type TaskBrokerDispatchOptions = {
spec: TaskSpec;
secrets?: TaskSecrets;
createdBy?: string;
};
/**
* Task
*
* @public
*/
export interface TaskContext {
cancelSignal: AbortSignal;
spec: TaskSpec;
secrets?: TaskSecrets;
createdBy?: string;
done: boolean;
isDryRun?: boolean;
complete(result: TaskCompletionState, metadata?: JsonObject): Promise<void>;
emitLog(message: string, logMetadata?: JsonObject): Promise<void>;
getWorkspaceName(): Promise<string>;
}
/**
* TaskBroker
*
* @public
*/
export interface TaskBroker {
cancel?(taskId: string): Promise<void>;
claim(): Promise<TaskContext>;
dispatch(
options: TaskBrokerDispatchOptions,
): Promise<TaskBrokerDispatchResult>;
vacuumTasks(options: { timeoutS: number }): Promise<void>;
event$(options: {
taskId: string;
after: number | undefined;
}): Observable<{ events: SerializedTaskEvent[] }>;
get(taskId: string): Promise<SerializedTask>;
list?(options?: { createdBy?: string }): Promise<{ tasks: SerializedTask[] }>;
}
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright 2023 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 { JsonValue } from '@backstage/types';
/** @public */
export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined;
/** @public */
export type TemplateGlobal =
| ((...args: JsonValue[]) => JsonValue | undefined)
| JsonValue;