breaking: removing old create router declaration in preparation for new backend systemt

Signed-off-by: benjdlambert <ben@blam.sh>

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
benjdlambert
2025-05-27 21:50:52 +02:00
parent 33394db631
commit a8fcf04d44
13 changed files with 106 additions and 258 deletions
+2
View File
@@ -2,6 +2,8 @@
'@backstage/plugin-scaffolder-backend': major
---
**BREAKING CHANGES**
Removal of deprecated re-exports from module packages.
The following functions have been re-exported from the `scaffolder-backend` plugin for quite some time, and now it's time to clean them up. They've been moved as follows:
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-scaffolder-backend': major
---
**BREAKING ALPHA**: The `/alpha` export no longer exports the plugin. Please use `import('@backstage/plugin-scaffolder-backend')` instead as this has been removed.
**BREAKING CHANGES**: The old `createRouter` function which was used in the old backend system has been removed along with the `RouterOptions` type.
+1 -1
View File
@@ -88,7 +88,6 @@
"@backstage/plugin-scaffolder-node": "workspace:^",
"@backstage/types": "workspace:^",
"@opentelemetry/api": "^1.9.0",
"@types/express": "^4.17.6",
"@types/luxon": "^3.0.0",
"concat-stream": "^2.0.0",
"express": "^4.17.1",
@@ -122,6 +121,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-scaffolder-node-test-utils": "workspace:^",
"@types/express": "^4.17.6",
"@types/fs-extra": "^11.0.0",
"@types/nunjucks": "^3.1.4",
"@types/supertest": "^2.0.8",
@@ -3,7 +3,6 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common';
import { Conditions } from '@backstage/plugin-permission-node';
import { JsonObject } from '@backstage/types';
@@ -26,10 +25,6 @@ export const createScaffolderTemplateConditionalDecision: (
conditions: PermissionCriteria<PermissionCondition<'scaffolder-template'>>,
) => ConditionalPolicyDecision;
// @alpha (undocumented)
const _feature: BackendFeature;
export default _feature;
// @alpha
export const scaffolderActionConditions: Conditions<{
hasActionId: PermissionRule<
-68
View File
@@ -5,33 +5,23 @@
```ts
import { AuditorService } from '@backstage/backend-plugin-api';
import { AuthService } from '@backstage/backend-plugin-api';
import { AutocompleteHandler } from '@backstage/plugin-scaffolder-node/alpha';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { BackstageCredentials } from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { Config } from '@backstage/config';
import { CreatedTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha';
import { CreatedTemplateGlobal } from '@backstage/plugin-scaffolder-node/alpha';
import { DatabaseService } from '@backstage/backend-plugin-api';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { Duration } from 'luxon';
import { EventsService } from '@backstage/plugin-events-node';
import express from 'express';
import { HttpAuthService } from '@backstage/backend-plugin-api';
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 { LifecycleService } from '@backstage/backend-plugin-api';
import { Logger } from 'winston';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
import { PermissionRule } from '@backstage/plugin-permission-node';
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
import { PermissionsService } from '@backstage/backend-plugin-api';
import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha';
import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha';
import { SchedulerService } from '@backstage/backend-plugin-api';
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';
@@ -248,9 +238,6 @@ export const createFilesystemRenameAction: () => TemplateAction<
'v1'
>;
// @public @deprecated
export function createRouter(options: RouterOptions): Promise<express.Router>;
// @public
export function createWaitAction(options?: {
maxWaitTime?: Duration | HumanDuration;
@@ -382,61 +369,6 @@ export type DatabaseTaskStoreOptions = {
events?: EventsService;
};
// @public @deprecated
export interface RouterOptions {
// (undocumented)
actions?: TemplateAction<any, any, any>[];
// (undocumented)
additionalTemplateFilters?:
| Record<string, TemplateFilter_2>
| CreatedTemplateFilter<any, any>[];
// (undocumented)
additionalTemplateGlobals?:
| Record<string, TemplateGlobal_2>
| CreatedTemplateGlobal[];
// (undocumented)
additionalWorkspaceProviders?: Record<string, WorkspaceProvider>;
// (undocumented)
auditor?: AuditorService;
// (undocumented)
auth?: AuthService;
// (undocumented)
autocompleteHandlers?: Record<string, AutocompleteHandler>;
// (undocumented)
catalogClient: CatalogApi;
concurrentTasksLimit?: number;
// (undocumented)
config: Config;
// (undocumented)
database: DatabaseService;
// (undocumented)
discovery?: DiscoveryService;
// (undocumented)
events?: EventsService;
// (undocumented)
httpAuth?: HttpAuthService;
// (undocumented)
identity?: IdentityApi;
// (undocumented)
lifecycle?: LifecycleService;
// (undocumented)
logger: Logger;
// (undocumented)
permissionRules?: Array<
TemplatePermissionRuleInput | ActionPermissionRuleInput
>;
// (undocumented)
permissions?: PermissionsService;
// (undocumented)
reader: UrlReaderService;
// (undocumented)
scheduler?: SchedulerService;
// (undocumented)
taskBroker?: TaskBroker_2;
// @deprecated (undocumented)
taskWorkers?: number;
}
// @public
const scaffolderPlugin: BackendFeature;
export default scaffolderPlugin;
@@ -134,7 +134,6 @@ export const scaffolderPlugin = createBackendPlugin({
permissions: coreServices.permissions,
database: coreServices.database,
auth: coreServices.auth,
discovery: coreServices.discovery,
httpRouter: coreServices.httpRouter,
httpAuth: coreServices.httpAuth,
auditor: coreServices.auditor,
@@ -148,7 +147,6 @@ export const scaffolderPlugin = createBackendPlugin({
reader,
database,
auth,
discovery,
httpRouter,
httpAuth,
catalogClient,
@@ -220,7 +218,6 @@ export const scaffolderPlugin = createBackendPlugin({
additionalTemplateGlobals,
auth,
httpAuth,
discovery,
permissions,
autocompleteHandlers,
additionalWorkspaceProviders,
+1 -7
View File
@@ -13,10 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { scaffolderPlugin } from './ScaffolderPlugin';
/** @alpha */
const _feature = scaffolderPlugin;
export default _feature;
export * from './service';
export * from './service/alpha';
+4 -1
View File
@@ -22,5 +22,8 @@
export { scaffolderPlugin as default } from './ScaffolderPlugin';
export * from './scaffolder';
export * from './service/router';
export * from './lib';
export {
type TemplatePermissionRuleInput,
type ActionPermissionRuleInput,
} from './service/permissions';
@@ -1,17 +0,0 @@
/*
* 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.
*/
export * from './conditionExports';
@@ -0,0 +1,61 @@
/*
* Copyright 2025 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 { PermissionRule } from '@backstage/plugin-permission-node';
import {
TemplateEntityStepV1beta3,
TemplateParametersV1beta3,
} from '@backstage/plugin-scaffolder-common';
import {
RESOURCE_TYPE_SCAFFOLDER_ACTION,
RESOURCE_TYPE_SCAFFOLDER_TEMPLATE,
} from '@backstage/plugin-scaffolder-common/alpha';
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
/**
* @public
*/
export type TemplatePermissionRuleInput<
TParams extends PermissionRuleParams = PermissionRuleParams,
> = PermissionRule<
TemplateEntityStepV1beta3 | TemplateParametersV1beta3,
{},
typeof RESOURCE_TYPE_SCAFFOLDER_TEMPLATE,
TParams
>;
export function isTemplatePermissionRuleInput(
permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput,
): permissionRule is TemplatePermissionRuleInput {
return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TEMPLATE;
}
/**
*
* @public
*/
export type ActionPermissionRuleInput<
TParams extends PermissionRuleParams = PermissionRuleParams,
> = PermissionRule<
TemplateEntityStepV1beta3 | TemplateParametersV1beta3,
{},
typeof RESOURCE_TYPE_SCAFFOLDER_ACTION,
TParams
>;
export function isActionPermissionRuleInput(
permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput,
): permissionRule is ActionPermissionRuleInput {
return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION;
}
@@ -32,7 +32,6 @@ import {
stringifyEntityRef,
UserEntity,
} from '@backstage/catalog-model';
import { createRouter, DatabaseTaskStore } from '../index';
import {
TaskBroker,
TemplateFilter,
@@ -66,6 +65,8 @@ import {
extractGlobalValueMetadata,
} from '../util/templating';
import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters';
import { createRouter } from './router';
import { DatabaseTaskStore } from '../scaffolder/tasks/DatabaseTaskStore';
const mockAccess = jest.fn();
@@ -194,7 +195,6 @@ describe.each([
} as unknown as PermissionEvaluator;
const auth = mockServices.auth();
const httpAuth = mockServices.httpAuth();
const discovery = mockServices.discovery();
const events = {
publish: jest.fn(),
} as unknown as EventsService;
@@ -309,7 +309,6 @@ describe.each([
permissions: permissionApi,
auth,
httpAuth,
discovery,
events,
additionalTemplateFilters,
additionalTemplateGlobals,
@@ -886,7 +885,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{
permissions: permissionApi,
auth,
httpAuth,
discovery,
});
app = express().use(router);
@@ -1657,7 +1655,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{
permissions: permissionApi,
auth,
httpAuth,
discovery,
autocompleteHandlers: {
'test-provider': handleAutocompleteRequest,
},
+28 -151
View File
@@ -14,13 +14,11 @@
* limitations under the License.
*/
import { createLegacyAuthAdapters } from '@backstage/backend-common';
import {
AuditorService,
AuthService,
BackstageCredentials,
DatabaseService,
DiscoveryService,
HttpAuthService,
LifecycleService,
PermissionsService,
@@ -37,25 +35,19 @@ import {
UserEntity,
} from '@backstage/catalog-model';
import { Config, readDurationFromConfig } from '@backstage/config';
import { InputError, NotFoundError, stringifyError } from '@backstage/errors';
import { InputError, NotFoundError } from '@backstage/errors';
import { ScmIntegrations } from '@backstage/integration';
import {
IdentityApi,
IdentityApiGetIdentityRequest,
} from '@backstage/plugin-auth-node';
import { EventsService } from '@backstage/plugin-events-node';
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
import {
createConditionAuthorizer,
createPermissionIntegrationRouter,
PermissionRule,
} from '@backstage/plugin-permission-node';
import {
TaskSpec,
TemplateEntityStepV1beta3,
TemplateEntityV1beta3,
templateEntityV1beta3Validator,
TemplateParametersV1beta3,
} from '@backstage/plugin-scaffolder-common';
import {
RESOURCE_TYPE_SCAFFOLDER_ACTION,
@@ -82,7 +74,7 @@ import {
CreatedTemplateGlobal,
WorkspaceProvider,
} from '@backstage/plugin-scaffolder-node/alpha';
import { HumanDuration, JsonObject, JsonValue } from '@backstage/types';
import { HumanDuration, JsonObject } from '@backstage/types';
import express from 'express';
import Router from 'express-promise-router';
import { validate } from 'jsonschema';
@@ -109,7 +101,6 @@ import {
parseStringsParam,
} from './helpers';
import { scaffolderActionRules, scaffolderTemplateRules } from './rules';
import { HostDiscovery } from '@backstage/backend-defaults/discovery';
import {
convertFiltersToRecord,
convertGlobalsToRecord,
@@ -118,48 +109,15 @@ import {
extractGlobalValueMetadata,
} from '../util/templating';
import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters';
/**
*
* @public
*/
export type TemplatePermissionRuleInput<
TParams extends PermissionRuleParams = PermissionRuleParams,
> = PermissionRule<
TemplateEntityStepV1beta3 | TemplateParametersV1beta3,
{},
typeof RESOURCE_TYPE_SCAFFOLDER_TEMPLATE,
TParams
>;
function isTemplatePermissionRuleInput(
permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput,
): permissionRule is TemplatePermissionRuleInput {
return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TEMPLATE;
}
/**
*
* @public
*/
export type ActionPermissionRuleInput<
TParams extends PermissionRuleParams = PermissionRuleParams,
> = PermissionRule<
TemplateEntityStepV1beta3 | TemplateParametersV1beta3,
{},
typeof RESOURCE_TYPE_SCAFFOLDER_ACTION,
TParams
>;
function isActionPermissionRuleInput(
permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput,
): permissionRule is ActionPermissionRuleInput {
return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION;
}
import {
ActionPermissionRuleInput,
isActionPermissionRuleInput,
isTemplatePermissionRuleInput,
TemplatePermissionRuleInput,
} from './permissions';
/**
* RouterOptions
*
* @public
* @deprecated Please migrate to the new backend system as this will be removed in the future.
*/
export interface RouterOptions {
logger: Logger;
@@ -170,11 +128,6 @@ export interface RouterOptions {
catalogClient: CatalogApi;
scheduler?: SchedulerService;
actions?: TemplateAction<any, any, any>[];
/**
* @deprecated taskWorkers is deprecated in favor of concurrentTasksLimit option with a single TaskWorker
* @defaultValue 1
*/
taskWorkers?: number;
/**
* Sets the number of concurrent tasks that can be run at any given time on the TaskWorker
* @defaultValue 10
@@ -192,10 +145,8 @@ export interface RouterOptions {
permissionRules?: Array<
TemplatePermissionRuleInput | ActionPermissionRuleInput
>;
auth?: AuthService;
httpAuth?: HttpAuthService;
identity?: IdentityApi;
discovery?: DiscoveryService;
auth: AuthService;
httpAuth: HttpAuthService;
events?: EventsService;
auditor?: AuditorService;
autocompleteHandlers?: Record<string, AutocompleteHandler>;
@@ -205,70 +156,6 @@ function isSupportedTemplate(entity: TemplateEntityV1beta3) {
return entity.apiVersion === 'scaffolder.backstage.io/v1beta3';
}
/*
* @deprecated This function remains as the DefaultIdentityClient behaves slightly differently to the pre-existing
* scaffolder behaviour. Specifically if the token fails to parse, the DefaultIdentityClient will raise an error.
* The scaffolder did not raise an error in this case. As such we chose to allow it to behave as it did previously
* until someone explicitly passes an IdentityApi. When we have reasonable confidence that most backstage deployments
* are using the IdentityApi, we can remove this function.
*/
function buildDefaultIdentityClient(options: RouterOptions): IdentityApi {
return {
getIdentity: async ({ request }: IdentityApiGetIdentityRequest) => {
const header = request.headers.authorization;
const { logger } = options;
if (!header) {
return undefined;
}
try {
const token = header.match(/^Bearer\s(\S+\.\S+\.\S+)$/i)?.[1];
if (!token) {
throw new TypeError('Expected Bearer with JWT');
}
const [_header, rawPayload, _signature] = token.split('.');
const payload: JsonValue = JSON.parse(
Buffer.from(rawPayload, 'base64').toString(),
);
if (
typeof payload !== 'object' ||
payload === null ||
Array.isArray(payload)
) {
throw new TypeError('Malformed JWT payload');
}
const sub = payload.sub;
if (typeof sub !== 'string') {
throw new TypeError('Expected string sub claim');
}
if (sub === 'backstage-server') {
return undefined;
}
// Check that it's a valid ref, otherwise this will throw.
parseEntityRef(sub);
return {
identity: {
userEntityRef: sub,
ownershipEntityRefs: [],
type: 'user',
},
token,
};
} catch (e) {
logger.error(`Invalid authorization header: ${stringifyError(e)}`);
return undefined;
}
},
};
}
const readDuration = (
config: Config,
key: string,
@@ -282,8 +169,6 @@ const readDuration = (
/**
* A method to create a router for the scaffolder backend plugin.
* @public
* @deprecated Please migrate to the new backend system as this will be removed in the future.
*/
export async function createRouter(
options: RouterOptions,
@@ -299,26 +184,19 @@ export async function createRouter(
database,
catalogClient,
actions,
taskWorkers,
scheduler,
additionalTemplateFilters,
additionalTemplateGlobals,
additionalWorkspaceProviders,
permissions,
permissionRules,
discovery = HostDiscovery.fromConfig(config),
identity = buildDefaultIdentityClient(options),
autocompleteHandlers = {},
events: eventsService,
auth,
httpAuth,
auditor,
} = options;
const { auth, httpAuth } = createLegacyAuthAdapters({
...options,
identity,
discovery,
});
const concurrentTasksLimit =
options.concurrentTasksLimit ??
options.config.getOptionalNumber('scaffolder.concurrentTasksLimit');
@@ -391,21 +269,20 @@ export async function createRouter(
'scaffolder.EXPERIMENTAL_gracefulShutdown',
);
for (let i = 0; i < (taskWorkers || 1); i++) {
const worker = await TaskWorker.create({
taskBroker,
actionRegistry,
integrations,
logger,
auditor,
workingDirectory,
concurrentTasksLimit,
permissions,
gracefulShutdown,
...templateExtensions,
});
workers.push(worker);
}
const worker = await TaskWorker.create({
taskBroker,
actionRegistry,
integrations,
logger,
auditor,
workingDirectory,
concurrentTasksLimit,
permissions,
gracefulShutdown,
...templateExtensions,
});
workers.push(worker);
}
const actionsToRegister = Array.isArray(actions)