diff --git a/.changeset/scaffolder-breakages-1.md b/.changeset/scaffolder-breakages-1.md index 50d94b9b70..53a7ba9d6a 100644 --- a/.changeset/scaffolder-breakages-1.md +++ b/.changeset/scaffolder-breakages-1.md @@ -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: diff --git a/.changeset/scaffolder-breakages-2.md b/.changeset/scaffolder-breakages-2.md new file mode 100644 index 0000000000..77ffe5fc42 --- /dev/null +++ b/.changeset/scaffolder-breakages-2.md @@ -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. diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index e59d8de4f0..a703d8818f 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -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", diff --git a/plugins/scaffolder-backend/report-alpha.api.md b/plugins/scaffolder-backend/report-alpha.api.md index c35b743f6a..958d68fc25 100644 --- a/plugins/scaffolder-backend/report-alpha.api.md +++ b/plugins/scaffolder-backend/report-alpha.api.md @@ -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>, ) => ConditionalPolicyDecision; -// @alpha (undocumented) -const _feature: BackendFeature; -export default _feature; - // @alpha export const scaffolderActionConditions: Conditions<{ hasActionId: PermissionRule< diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index bdce13d8e6..769403df9a 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -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; - // @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[]; - // (undocumented) - additionalTemplateFilters?: - | Record - | CreatedTemplateFilter[]; - // (undocumented) - additionalTemplateGlobals?: - | Record - | CreatedTemplateGlobal[]; - // (undocumented) - additionalWorkspaceProviders?: Record; - // (undocumented) - auditor?: AuditorService; - // (undocumented) - auth?: AuthService; - // (undocumented) - autocompleteHandlers?: Record; - // (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; diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index 9fe78c7119..fbfb75fe21 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -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, diff --git a/plugins/scaffolder-backend/src/alpha.ts b/plugins/scaffolder-backend/src/alpha.ts index 7d1d8fe0ef..438d16303d 100644 --- a/plugins/scaffolder-backend/src/alpha.ts +++ b/plugins/scaffolder-backend/src/alpha.ts @@ -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'; diff --git a/plugins/scaffolder-backend/src/index.ts b/plugins/scaffolder-backend/src/index.ts index b94915678c..e7f2de94a9 100644 --- a/plugins/scaffolder-backend/src/index.ts +++ b/plugins/scaffolder-backend/src/index.ts @@ -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'; diff --git a/plugins/scaffolder-backend/src/service/conditionExports.ts b/plugins/scaffolder-backend/src/service/alpha.ts similarity index 100% rename from plugins/scaffolder-backend/src/service/conditionExports.ts rename to plugins/scaffolder-backend/src/service/alpha.ts diff --git a/plugins/scaffolder-backend/src/service/index.ts b/plugins/scaffolder-backend/src/service/index.ts deleted file mode 100644 index dc5508d47f..0000000000 --- a/plugins/scaffolder-backend/src/service/index.ts +++ /dev/null @@ -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'; diff --git a/plugins/scaffolder-backend/src/service/permissions.ts b/plugins/scaffolder-backend/src/service/permissions.ts new file mode 100644 index 0000000000..9b12e115b9 --- /dev/null +++ b/plugins/scaffolder-backend/src/service/permissions.ts @@ -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; +} diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index d26cc955b3..8808e516ee 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -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, }, diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index f2e323e831..f451e41851 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -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[]; - /** - * @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; @@ -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)