From d779e3b055d8669a77729ba1bc15d61ad132e870 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Sun, 31 Mar 2024 11:46:02 +0530 Subject: [PATCH 001/118] fix not to add git commit link in about card edit button Signed-off-by: npiyush97 --- .changeset/thirty-plums-shout.md | 5 ++ .../AnnotateLocationEntityProcessor.test.ts | 48 +++++++++++++++++++ .../core/AnnotateLocationEntityProcessor.ts | 6 ++- 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 .changeset/thirty-plums-shout.md diff --git a/.changeset/thirty-plums-shout.md b/.changeset/thirty-plums-shout.md new file mode 100644 index 0000000000..5b5ffad6a9 --- /dev/null +++ b/.changeset/thirty-plums-shout.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Added a regex test to check commit hash.If url is from git commit branch ignore the edit url. diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts index 66b6300df9..2656de1c7b 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts @@ -166,5 +166,53 @@ describe('AnnotateLocationEntityProcessor', () => { }, }); }); + it('should not render edit button in about for invalid or git hash commit branch', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }; + + const location: LocationSpec = { + type: 'url', + target: + 'https://github.com/backstage/backstage/blob/f1ba2bc6097a757c2827ff97fa301cff626de137/packages/app/catalog-info.yaml', + }; + const originLocation: LocationSpec = { + type: 'url', + target: + 'https://github.com/backstage/backstage/blob/f1ba2bc6097a757c2827ff97fa301cff626de137/catalog-info.yaml', + }; + + const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); + const processor = new AnnotateLocationEntityProcessor({ integrations }); + + expect( + await processor.preProcessEntity( + entity, + location, + () => {}, + originLocation, + ), + ).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'backstage.io/managed-by-location': + 'url:https://github.com/backstage/backstage/blob/f1ba2bc6097a757c2827ff97fa301cff626de137/packages/app/catalog-info.yaml', + 'backstage.io/managed-by-origin-location': + 'url:https://github.com/backstage/backstage/blob/f1ba2bc6097a757c2827ff97fa301cff626de137/catalog-info.yaml', + 'backstage.io/view-url': + 'https://github.com/backstage/backstage/blob/f1ba2bc6097a757c2827ff97fa301cff626de137/packages/app/catalog-info.yaml', + 'backstage.io/source-location': + 'url:https://github.com/backstage/backstage/tree/f1ba2bc6097a757c2827ff97fa301cff626de137/packages/app/', + }, + }, + }); + }); }); }); diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts index d3329fa86c..1ff4963870 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts @@ -53,12 +53,16 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor { let viewUrl; let editUrl; let sourceLocation; + const gitCommitBranchURLPattern = /\b[0-9a-f]{5,40}\b/; if (location.type === 'url') { const scmIntegration = integrations.byUrl(location.target); viewUrl = location.target; - editUrl = scmIntegration?.resolveEditUrl(location.target); + + if (!gitCommitBranchURLPattern.test(location.target)) { + editUrl = scmIntegration?.resolveEditUrl(location.target); + } const sourceUrl = scmIntegration?.resolveUrl({ url: './', From 7c1540d46ed22ca1980e0fa89640c72ca103d3a4 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Tue, 16 Apr 2024 15:09:22 +0530 Subject: [PATCH 002/118] checking url is sha1 hash of len 40 Signed-off-by: npiyush97 --- .../src/modules/core/AnnotateLocationEntityProcessor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts index 1ff4963870..83638d205f 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts @@ -53,7 +53,7 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor { let viewUrl; let editUrl; let sourceLocation; - const gitCommitBranchURLPattern = /\b[0-9a-f]{5,40}\b/; + const gitCommitBranchURLPattern = /\b[0-9a-f]{40,}\b/; if (location.type === 'url') { const scmIntegration = integrations.byUrl(location.target); From 9ee832c5a940c97c2f43b9fa0a0cd65efc6527df Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 19 Apr 2024 16:48:12 -0400 Subject: [PATCH 003/118] feat(scaffolder): add additional scaffolder task permissions Signed-off-by: Frank Kong --- plugins/scaffolder-common/src/permissions.ts | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/plugins/scaffolder-common/src/permissions.ts b/plugins/scaffolder-common/src/permissions.ts index 5c4c367889..bd548df950 100644 --- a/plugins/scaffolder-common/src/permissions.ts +++ b/plugins/scaffolder-common/src/permissions.ts @@ -30,6 +30,13 @@ export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; */ export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; +/** + * Permission resource type which corresponds to a scaffolder task. + * + * @alpha + */ +export const RESOURCE_TYPE_SCAFFOLDER_TASK = 'scaffolder-task'; + /** * This permission is used to authorize actions that involve executing * an action from a template. @@ -78,6 +85,50 @@ export const templateStepReadPermission = createPermission({ resourceType: RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, }); +/** + * This permission is used to authorize actions that involve reading one or more tasks in the scaffolder, + * and reading logs of tasks + * + * Task cancellation would also require this permission. + * + * @alpha + */ +export const taskReadPermission = createPermission({ + name: 'scaffolder.task.read', + attributes: { + action: 'read', + }, + resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, +}); + +/** + * This permission is used to authorize actions that involve the creation of tasks in the scaffolder. + * + * @alpha + */ +export const taskCreatePermission = createPermission({ + name: 'scaffolder.task.create', + attributes: { + action: 'create', + }, + resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, +}); + +/** + * This permission us used to authorize actions that involve the cancellation of tasks in the scaffolder. + * + * This will require the `scaffolder.task.read` permission to be authorized. + * + * @alpha + */ +export const taskCancelPermission = createPermission({ + name: 'scaffolder.task.cancel', + attributes: { + action: 'update', + }, + resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, +}); + /** * List of all the scaffolder permissions * @alpha @@ -102,3 +153,13 @@ export const scaffolderTemplatePermissions = [ * @alpha */ export const scaffolderActionPermissions = [actionExecutePermission]; + +/** + * List of the scaffolder permissions that are associated with scaffolder tasks. + * @alpha + */ +export const scaffolderTaskPermissions = [ + taskCancelPermission, + taskCreatePermission, + taskReadPermission, +]; From a7b500bd0829bb2a449dd488377385a76b73aeb4 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Mon, 22 Apr 2024 17:15:22 -0400 Subject: [PATCH 004/118] feat(scaffolder): added permissions to backend endpoints Signed-off-by: Frank Kong --- .../scaffolder-backend/src/service/router.ts | 177 +++++++++++++++++- plugins/scaffolder-common/src/permissions.ts | 21 ++- 2 files changed, 184 insertions(+), 14 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 43618ed5a7..7b39290cfc 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -30,7 +30,12 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; -import { InputError, NotFoundError, stringifyError } from '@backstage/errors'; +import { + InputError, + NotAllowedError, + NotFoundError, + stringifyError, +} from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { @@ -43,10 +48,16 @@ import { import { RESOURCE_TYPE_SCAFFOLDER_ACTION, RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, + RESOURCE_TYPE_SCAFFOLDER_TASK, scaffolderActionPermissions, scaffolderTemplatePermissions, + taskCancelPermission, + taskCreatePermission, + taskReadPermission, templateParameterReadPermission, templateStepReadPermission, + scaffolderTaskPermissions, + actionReadPermission, } from '@backstage/plugin-scaffolder-common/alpha'; import express from 'express'; import Router from 'express-promise-router'; @@ -68,7 +79,10 @@ import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; import { findTemplate, getEntityBaseUrl, getWorkingDirectory } from './helpers'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { PermissionRuleParams } from '@backstage/plugin-permission-common'; +import { + AuthorizeResult, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; import { createConditionAuthorizer, createPermissionIntegrationRouter, @@ -90,6 +104,11 @@ import { } from '@backstage/plugin-auth-node'; import { InternalTaskSecrets } from '../scaffolder/tasks/types'; +type ScaffolderPermissionRuleInput = + | TemplatePermissionRuleInput + | ActionPermissionRuleInput + | TaskPermissionRuleInput; + /** * * @public @@ -103,7 +122,7 @@ export type TemplatePermissionRuleInput< TParams >; function isTemplatePermissionRuleInput( - permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, + permissionRule: ScaffolderPermissionRuleInput, ): permissionRule is TemplatePermissionRuleInput { return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TEMPLATE; } @@ -121,11 +140,28 @@ export type ActionPermissionRuleInput< TParams >; function isActionPermissionRuleInput( - permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, + permissionRule: ScaffolderPermissionRuleInput, ): permissionRule is ActionPermissionRuleInput { return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION; } +/** + * + * @public + */ +export type TaskPermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule< + TemplateEntityStepV1beta3 | TemplateParametersV1beta3, + {}, + typeof RESOURCE_TYPE_SCAFFOLDER_TASK, + TParams +>; +function isTaskPermissionRuleInput( + permissionRule: ScaffolderPermissionRuleInput, +): permissionRule is TaskPermissionRuleInput { + return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TASK; +} /** * RouterOptions * @@ -154,9 +190,7 @@ export interface RouterOptions { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; permissions?: PermissionsService; - permissionRules?: Array< - TemplatePermissionRuleInput | ActionPermissionRuleInput - >; + permissionRules?: Array; auth?: AuthService; httpAuth?: HttpAuthService; identity?: IdentityApi; @@ -384,12 +418,14 @@ export async function createRouter( const actionRules: ActionPermissionRuleInput[] = Object.values( scaffolderActionRules, ); + const taskRules: TaskPermissionRuleInput[] = []; if (permissionRules) { templateRules.push( ...permissionRules.filter(isTemplatePermissionRuleInput), ); actionRules.push(...permissionRules.filter(isActionPermissionRuleInput)); + taskRules.push(...permissionRules.filter(isTaskPermissionRuleInput)); } const isAuthorized = createConditionAuthorizer(Object.values(templateRules)); @@ -406,6 +442,11 @@ export async function createRouter( permissions: scaffolderActionPermissions, rules: actionRules, }, + { + resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, + permissions: scaffolderTaskPermissions, + rules: taskRules, + }, ], }); @@ -445,7 +486,21 @@ export async function createRouter( }); }, ) - .get('/v2/actions', async (_req, res) => { + .get('/v2/actions', async (req, res) => { + const credentials = await httpAuth.credentials(req); + + if (permissions) { + const authorizationResponse = ( + await permissions?.authorizeConditional( + [{ permission: actionReadPermission }], + { credentials: credentials }, + ) + )[0]; + + if (authorizationResponse.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } const actionsList = actionRegistry.list().map(action => { return { id: action.id, @@ -463,6 +518,19 @@ export async function createRouter( }); const credentials = await httpAuth.credentials(req); + if (permissions) { + const authorizationResponse = ( + await permissions?.authorizeConditional( + [{ permission: taskCreatePermission }], + { credentials: credentials }, + ) + )[0]; + + if (authorizationResponse.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } + const { token } = await auth.getPluginRequestToken({ onBehalfOf: credentials, targetPluginId: 'catalog', @@ -539,6 +607,21 @@ export async function createRouter( res.status(201).json({ id: result.taskId }); }) .get('/v2/tasks', async (req, res) => { + const credentials = await httpAuth.credentials(req); + + if (permissions) { + const authorizationResponse = ( + await permissions?.authorizeConditional( + [{ permission: taskReadPermission }], + { credentials: credentials }, + ) + )[0]; + + if (authorizationResponse.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } + const [userEntityRef] = [req.query.createdBy].flat(); if ( @@ -561,6 +644,21 @@ export async function createRouter( res.status(200).json(tasks); }) .get('/v2/tasks/:taskId', async (req, res) => { + const credentials = await httpAuth.credentials(req); + + if (permissions) { + const authorizationResponse = ( + await permissions?.authorizeConditional( + [{ permission: taskReadPermission }], + { credentials: credentials }, + ) + )[0]; + + if (authorizationResponse.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } + const { taskId } = req.params; const task = await taskBroker.get(taskId); if (!task) { @@ -571,11 +669,43 @@ export async function createRouter( res.status(200).json(task); }) .post('/v2/tasks/:taskId/cancel', async (req, res) => { + const credentials = await httpAuth.credentials(req); + + if (permissions) { + const authorizationResponses = await permissions?.authorizeConditional( + [ + { permission: taskCancelPermission }, + { permission: taskReadPermission }, + ], + { credentials: credentials }, + ); + // Requires both read and cancel permissions + for (const response of authorizationResponses) { + if (response.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } + } + const { taskId } = req.params; await taskBroker.cancel?.(taskId); res.status(200).json({ status: 'cancelled' }); }) .get('/v2/tasks/:taskId/eventstream', async (req, res) => { + const credentials = await httpAuth.credentials(req); + + if (permissions) { + const authorizationResponse = ( + await permissions?.authorizeConditional( + [{ permission: taskReadPermission }], + { credentials: credentials }, + ) + )[0]; + + if (authorizationResponse.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } const { taskId } = req.params; const after = req.query.after !== undefined ? Number(req.query.after) : undefined; @@ -624,6 +754,20 @@ export async function createRouter( }); }) .get('/v2/tasks/:taskId/events', async (req, res) => { + const credentials = await httpAuth.credentials(req); + + if (permissions) { + const authorizationResponse = ( + await permissions?.authorizeConditional( + [{ permission: taskReadPermission }], + { credentials: credentials }, + ) + )[0]; + + if (authorizationResponse.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } const { taskId } = req.params; const after = Number(req.query.after) || undefined; @@ -654,6 +798,21 @@ export async function createRouter( }); }) .post('/v2/dry-run', async (req, res) => { + const credentials = await httpAuth.credentials(req); + + if (permissions) { + const authorizationResponse = ( + await permissions?.authorizeConditional( + [{ permission: taskCreatePermission }], + { credentials: credentials }, + ) + )[0]; + + if (authorizationResponse.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } + const bodySchema = z.object({ template: z.unknown(), values: z.record(z.unknown()), @@ -671,8 +830,6 @@ export async function createRouter( throw new InputError('Input template is not a template'); } - const credentials = await httpAuth.credentials(req); - const { token } = await auth.getPluginRequestToken({ onBehalfOf: credentials, targetPluginId: 'catalog', diff --git a/plugins/scaffolder-common/src/permissions.ts b/plugins/scaffolder-common/src/permissions.ts index bd548df950..4a4137875a 100644 --- a/plugins/scaffolder-common/src/permissions.ts +++ b/plugins/scaffolder-common/src/permissions.ts @@ -49,6 +49,18 @@ export const actionExecutePermission = createPermission({ resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, }); +/** + * This permission is used to authorize actions that involve access the action registry + * + * @alpha + */ +export const actionReadPermission = createPermission({ + name: 'scaffolder.action.read', + attributes: { + action: 'read', + }, + resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, +}); /** * This permission is used to authorize actions that involve reading * one or more parameters from a template. @@ -123,9 +135,7 @@ export const taskCreatePermission = createPermission({ */ export const taskCancelPermission = createPermission({ name: 'scaffolder.task.cancel', - attributes: { - action: 'update', - }, + attributes: {}, resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, }); @@ -152,7 +162,10 @@ export const scaffolderTemplatePermissions = [ * List of the scaffolder permissions that are associated with scaffolder actions. * @alpha */ -export const scaffolderActionPermissions = [actionExecutePermission]; +export const scaffolderActionPermissions = [ + actionExecutePermission, + actionReadPermission, +]; /** * List of the scaffolder permissions that are associated with scaffolder tasks. From b7fcaca26bbddf7d8b86214f4634526f33dac711 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 25 Apr 2024 16:42:23 -0400 Subject: [PATCH 005/118] feat(scaffolder): update scaffolder frontend to support additional backend permissions Signed-off-by: Frank Kong --- .../components/AboutCard/AboutCard.test.tsx | 61 ++++++++++++++- .../src/components/AboutCard/AboutCard.tsx | 11 ++- .../scaffolder-backend/src/service/router.ts | 18 ++--- plugins/scaffolder-react/package.json | 5 +- .../TemplateCard/TemplateCard.test.tsx | 57 ++++++++++++++ .../components/TemplateCard/TemplateCard.tsx | 25 ++++-- plugins/scaffolder/dev/index.tsx | 3 +- .../components/ActionsPage/ActionsPage.tsx | 47 ++++++----- .../ListTasksPage/ListTasksPage.tsx | 2 +- .../components/OngoingTask/ContextMenu.tsx | 40 +++++++++- .../OngoingTask/OngoingTask.test.tsx | 77 ++++++++++++------- .../components/OngoingTask/OngoingTask.tsx | 37 ++++++++- 12 files changed, 309 insertions(+), 74 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index f90942dcd8..76d41399ba 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -35,6 +35,7 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { permissionApiRef } from '@backstage/plugin-permission-react'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { SWRConfig } from 'swr'; const mockAuthorize = jest.fn(); @@ -546,7 +547,7 @@ describe('', () => { ).not.toBeInTheDocument(); }); - it('renders techdocs lin when 3rdparty', async () => { + it('renders techdocs link when 3rdparty', async () => { const entity = { apiVersion: 'v1', kind: 'Component', @@ -774,7 +775,9 @@ describe('', () => { namespace: 'default', }, }; - + mockAuthorize.mockImplementation(async () => ({ + result: AuthorizeResult.ALLOW, + })); await renderInTestApp( ', () => { ), ], [catalogApiRef, catalogApi], - [permissionApiRef, {}], + [permissionApiRef, mockPermissionApi], ]} > @@ -816,6 +819,58 @@ describe('', () => { '/create/templates/default/create-react-app-template', ); }); + it('renders disabled launch template button if user has insufficient permissions', async () => { + const entity = { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + name: 'create-react-app-template', + namespace: 'default', + }, + }; + mockAuthorize.mockImplementation(async () => ({ + result: AuthorizeResult.DENY, + })); + const rendered = await renderInTestApp( + new Map() }}> + + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + '/create/templates/:namespace/:templateName': + createFromTemplateRouteRef, + }, + }, + ); + + expect(screen.getByText('Launch Template')).toBeVisible(); + expect(screen.getByText('Launch Template').closest('a')).toBeNull(); + }); it.each([ { diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index a24a132bc4..b63666f80c 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -19,6 +19,7 @@ import { CompoundEntityRef, DEFAULT_NAMESPACE, stringifyEntityRef, + parseEntityRef, } from '@backstage/catalog-model'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; @@ -58,10 +59,11 @@ import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import DocsIcon from '@material-ui/icons/Description'; import EditIcon from '@material-ui/icons/Edit'; import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { parseEntityRef } from '@backstage/catalog-model'; import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha'; import { useSourceTemplateCompoundEntityRef } from './hooks'; +import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha'; +import { usePermission } from '@backstage/plugin-permission-react'; const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; @@ -114,6 +116,11 @@ export function AboutCard(props: AboutCardProps) { const { allowed: canRefresh } = useEntityPermission( catalogEntityRefreshPermission, ); + const { kind, name, namespace } = entity.metadata; + const { allowed: canCreateTemplateTask } = usePermission({ + permission: taskCreatePermission, + resourceRef: `${kind}:${namespace}/${name}`, + }); const entitySourceLocation = getEntitySourceLocation( entity, @@ -172,7 +179,7 @@ export function AboutCard(props: AboutCardProps) { const launchTemplate: IconLinkVerticalProps = { label: 'Launch Template', icon: , - disabled: !templateRoute, + disabled: !templateRoute || !canCreateTemplateTask, href: templateRoute && templateRoute({ diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 7b39290cfc..3fd1e14fa1 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -65,6 +65,7 @@ import { validate } from 'jsonschema'; import { Logger } from 'winston'; import { z } from 'zod'; import { + TemplateAction, TaskBroker, TemplateFilter, TemplateGlobal, @@ -78,7 +79,6 @@ import { import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; import { findTemplate, getEntityBaseUrl, getWorkingDirectory } from './helpers'; -import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { AuthorizeResult, PermissionRuleParams, @@ -491,7 +491,7 @@ export async function createRouter( if (permissions) { const authorizationResponse = ( - await permissions?.authorizeConditional( + await permissions.authorizeConditional( [{ permission: actionReadPermission }], { credentials: credentials }, ) @@ -520,7 +520,7 @@ export async function createRouter( const credentials = await httpAuth.credentials(req); if (permissions) { const authorizationResponse = ( - await permissions?.authorizeConditional( + await permissions.authorizeConditional( [{ permission: taskCreatePermission }], { credentials: credentials }, ) @@ -611,7 +611,7 @@ export async function createRouter( if (permissions) { const authorizationResponse = ( - await permissions?.authorizeConditional( + await permissions.authorizeConditional( [{ permission: taskReadPermission }], { credentials: credentials }, ) @@ -648,7 +648,7 @@ export async function createRouter( if (permissions) { const authorizationResponse = ( - await permissions?.authorizeConditional( + await permissions.authorizeConditional( [{ permission: taskReadPermission }], { credentials: credentials }, ) @@ -672,7 +672,7 @@ export async function createRouter( const credentials = await httpAuth.credentials(req); if (permissions) { - const authorizationResponses = await permissions?.authorizeConditional( + const authorizationResponses = await permissions.authorizeConditional( [ { permission: taskCancelPermission }, { permission: taskReadPermission }, @@ -696,7 +696,7 @@ export async function createRouter( if (permissions) { const authorizationResponse = ( - await permissions?.authorizeConditional( + await permissions.authorizeConditional( [{ permission: taskReadPermission }], { credentials: credentials }, ) @@ -758,7 +758,7 @@ export async function createRouter( if (permissions) { const authorizationResponse = ( - await permissions?.authorizeConditional( + await permissions.authorizeConditional( [{ permission: taskReadPermission }], { credentials: credentials }, ) @@ -802,7 +802,7 @@ export async function createRouter( if (permissions) { const authorizationResponse = ( - await permissions?.authorizeConditional( + await permissions.authorizeConditional( [{ permission: taskCreatePermission }], { credentials: credentials }, ) diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index ad12268baa..0b803f35e4 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -54,6 +54,7 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/plugin-permission-react": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", @@ -87,13 +88,15 @@ "@backstage/core-app-api": "workspace:^", "@backstage/plugin-catalog": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^15.0.0", "@testing-library/user-event": "^14.0.0", "@types/humanize-duration": "^3.18.1", - "@types/luxon": "^3.0.0" + "@types/luxon": "^3.0.0", + "swr": "^2.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx index ec4e4e1b0a..90ae1e097c 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx @@ -19,6 +19,7 @@ import { starredEntitiesApiRef, } from '@backstage/plugin-catalog-react'; import { + MockPermissionApi, MockStorageApi, renderInTestApp, TestApiProvider, @@ -28,6 +29,12 @@ import React from 'react'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { RELATION_OWNED_BY } from '@backstage/catalog-model'; import { fireEvent } from '@testing-library/react'; +import { + PermissionApi, + permissionApiRef, +} from '@backstage/plugin-permission-react'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { SWRConfig } from 'swr'; describe('TemplateCard', () => { it('should render the card title', async () => { @@ -50,6 +57,7 @@ describe('TemplateCard', () => { storageApi: MockStorageApi.create(), }), ], + [permissionApiRef, new MockPermissionApi()], ]} > @@ -79,6 +87,7 @@ describe('TemplateCard', () => { storageApi: MockStorageApi.create(), }), ], + [permissionApiRef, new MockPermissionApi()], ]} > @@ -110,6 +119,7 @@ describe('TemplateCard', () => { storageApi: MockStorageApi.create(), }), ], + [permissionApiRef, new MockPermissionApi()], ]} > @@ -139,6 +149,7 @@ describe('TemplateCard', () => { storageApi: MockStorageApi.create(), }), ], + [permissionApiRef, new MockPermissionApi()], ]} > @@ -174,6 +185,7 @@ describe('TemplateCard', () => { storageApi: MockStorageApi.create(), }), ], + [permissionApiRef, new MockPermissionApi()], ]} > @@ -213,6 +225,7 @@ describe('TemplateCard', () => { storageApi: MockStorageApi.create(), }), ], + [permissionApiRef, new MockPermissionApi()], ]} > @@ -257,6 +270,7 @@ describe('TemplateCard', () => { storageApi: MockStorageApi.create(), }), ], + [permissionApiRef, new MockPermissionApi()], ]} > @@ -305,6 +319,7 @@ describe('TemplateCard', () => { storageApi: MockStorageApi.create(), }), ], + [permissionApiRef, new MockPermissionApi()], ]} > @@ -347,6 +362,7 @@ describe('TemplateCard', () => { storageApi: MockStorageApi.create(), }), ], + [permissionApiRef, new MockPermissionApi()], ]} > @@ -386,6 +402,7 @@ describe('TemplateCard', () => { storageApi: MockStorageApi.create(), }), ], + [permissionApiRef, new MockPermissionApi()], ]} > @@ -403,4 +420,44 @@ describe('TemplateCard', () => { expect(mockOnSelected).toHaveBeenCalledWith(mockTemplate); }); + it('should not render the choose button when user has insufficient permissions', async () => { + const mockTemplate: TemplateEntityV1beta3 = { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'bob', tags: ['cpp', 'react'] }, + spec: { + steps: [], + type: 'service', + }, + }; + const mockOnSelected = jest.fn(); + const mockAuthorize = jest + .fn() + .mockImplementation(async () => ({ result: AuthorizeResult.DENY })); + // SWR used by the usePermission hook needs cache to be reset for each test + const { queryByText } = await renderInTestApp( + new Map() }}> + + + + , + { + mountedRoutes: { + '/catalog/:kind/:namespace/:name': entityRouteRef, + }, + }, + ); + + expect(queryByText('Choose')).toBeNull(); + }); }); diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx index 4fb2ca0e4a..23e2f13e78 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx @@ -35,6 +35,8 @@ import LanguageIcon from '@material-ui/icons/Language'; import React from 'react'; import { CardHeader } from './CardHeader'; import { CardLink } from './CardLink'; +import { usePermission } from '@backstage/plugin-permission-react'; +import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha'; const useStyles = makeStyles(theme => ({ box: { @@ -103,6 +105,11 @@ export const TemplateCard = (props: TemplateCardProps) => { !!props.additionalLinks?.length || !!template.metadata.links?.length; const displayDefaultDivider = !hasTags && !hasLinks; + const { allowed: canCreateTask } = usePermission({ + permission: taskCreatePermission, + resourceRef: 'task', + }); + return ( @@ -186,14 +193,16 @@ export const TemplateCard = (props: TemplateCardProps) => { )} - + {canCreateTask ? ( + + ) : null} diff --git a/plugins/scaffolder/dev/index.tsx b/plugins/scaffolder/dev/index.tsx index e4425b04bf..ebc0c3596f 100644 --- a/plugins/scaffolder/dev/index.tsx +++ b/plugins/scaffolder/dev/index.tsx @@ -23,7 +23,8 @@ import { MockStarredEntitiesApi, } from '@backstage/plugin-catalog-react'; import React from 'react'; -import { scaffolderApiRef, ScaffolderClient } from '../src'; +import { ScaffolderClient } from '../src'; +import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; import { ScaffolderPage } from '../src/plugin'; import { discoveryApiRef, diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index d6eabb0e5c..7e859e272d 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -43,7 +43,8 @@ import { useApi, useRouteRef } from '@backstage/core-plugin-api'; import { CodeSnippet, Content, - ErrorPage, + EmptyState, + ErrorPanel, Header, MarkdownContent, Page, @@ -112,19 +113,9 @@ const ExamplesTable = (props: { examples: ActionExample[] }) => { ); }; -export const ActionsPage = () => { +const ActionPageContent = () => { const api = useApi(scaffolderApiRef); - const navigate = useNavigate(); - const editorLink = useRouteRef(editRouteRef); - const tasksLink = useRouteRef(scaffolderListTaskRouteRef); - const createLink = useRouteRef(rootRouteRef); - const scaffolderPageContextMenuProps = { - onEditorClicked: () => navigate(editorLink()), - onActionsClicked: undefined, - onTasksClicked: () => navigate(tasksLink()), - onCreateClicked: () => navigate(createLink()), - }; const classes = useStyles(); const { loading, value, error } = useAsync(async () => { return api.listActions(); @@ -137,11 +128,14 @@ export const ActionsPage = () => { if (error) { return ( - + <> + + + ); } @@ -282,7 +276,7 @@ export const ActionsPage = () => { ); }; - const items = value?.map(action => { + return value?.map(action => { if (action.id.startsWith('legacy:')) { return undefined; } @@ -336,6 +330,19 @@ export const ActionsPage = () => { ); }); +}; +export const ActionsPage = () => { + const navigate = useNavigate(); + const editorLink = useRouteRef(editRouteRef); + const tasksLink = useRouteRef(scaffolderListTaskRouteRef); + const createLink = useRouteRef(rootRouteRef); + + const scaffolderPageContextMenuProps = { + onEditorClicked: () => navigate(editorLink()), + onActionsClicked: undefined, + onTasksClicked: () => navigate(tasksLink()), + onCreateClicked: () => navigate(createLink()), + }; return ( @@ -346,7 +353,9 @@ export const ActionsPage = () => { > - {items} + + + ); }; diff --git a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx index 4c2eb04c79..d37716a710 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx @@ -77,7 +77,7 @@ const ListTaskPageContent = (props: MyTaskPageProps) => { ); diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index 99d9235791..bac23da67d 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -30,6 +30,12 @@ import MoreVert from '@material-ui/icons/MoreVert'; import React, { useState } from 'react'; import { useApi } from '@backstage/core-plugin-api'; import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; +import { usePermission } from '@backstage/plugin-permission-react'; +import { + taskCancelPermission, + taskReadPermission, + taskCreatePermission, +} from '@backstage/plugin-scaffolder-common/alpha'; type ContextMenuProps = { cancelEnabled?: boolean; @@ -69,6 +75,28 @@ export const ContextMenu = (props: ContextMenuProps) => { } }); + // Used dummy string value for `resourceRef` since `allowed` field will always return `false` if `resourceRef` is `undefined` + const { allowed: canCancelTask } = usePermission({ + permission: taskCancelPermission, + resourceRef: 'task', + }); + + const { allowed: canReadTask } = usePermission({ + permission: taskReadPermission, + resourceRef: 'task', + }); + + const { allowed: canCreateTask } = usePermission({ + permission: taskCreatePermission, + resourceRef: 'task', + }); + + // Cancel endpoint requires user to have both read and cancel permissions + const cancelNotAllowed = !(canReadTask && canCancelTask); + + // Start Over endpoint requires user to have both read (to grab parameters) and create (to create new task) permissions + const canStartOver = canReadTask && canCreateTask; + return ( <> { primary={buttonBarVisible ? 'Hide Button Bar' : 'Show Button Bar'} /> - + @@ -113,7 +145,11 @@ export const ContextMenu = (props: ContextMenuProps) => { diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx index 9d3f29fe7b..70d2193470 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx @@ -16,10 +16,20 @@ import { OngoingTask } from './OngoingTask'; import React from 'react'; -import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { + renderInTestApp, + TestApiProvider, + MockPermissionApi, +} from '@backstage/test-utils'; import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; import { act, fireEvent, waitFor, within } from '@testing-library/react'; +import { + PermissionApi, + permissionApiRef, +} from '@backstage/plugin-permission-react'; import { rootRouteRef } from '../../routes'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { SWRConfig } from 'swr'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -49,18 +59,29 @@ describe('OngoingTask', () => { getTask: jest.fn().mockImplementation(async () => {}), }; - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); }); - it('should trigger cancel api on "Cancel" click in context menu', async () => { - const cancelOptionLabel = 'Cancel'; - const rendered = await renderInTestApp( - - - , + const render = (permissionApi?: PermissionApi) => { + // SWR used by the usePermission hook needs cache to be reset for each test + return renderInTestApp( + new Map() }}> + + + + , { mountedRoutes: { '/': rootRouteRef } }, ); + }; + it('should trigger cancel api on "Cancel" click in context menu', async () => { + const rendered = await render(); + const cancelOptionLabel = 'Cancel'; const { getByTestId } = rendered; await act(async () => { @@ -84,13 +105,9 @@ describe('OngoingTask', () => { }); it('should trigger cancel api on "Cancel" button click', async () => { + const rendered = await render(); const cancelOptionLabel = 'Cancel'; - const rendered = await renderInTestApp( - - - , - { mountedRoutes: { '/': rootRouteRef } }, - ); + const { getByTestId } = rendered; await act(async () => { @@ -114,22 +131,12 @@ describe('OngoingTask', () => { }); it('should initially do not display logs', async () => { - const rendered = await renderInTestApp( - - - , - { mountedRoutes: { '/': rootRouteRef } }, - ); + const rendered = await render(); await expect(rendered.findByText('Show Logs')).resolves.toBeInTheDocument(); }); it('should toggle logs visibility', async () => { - const rendered = await renderInTestApp( - - - , - { mountedRoutes: { '/': rootRouteRef } }, - ); + const rendered = await render(); await act(async () => { const element = await rendered.findByText('Show Logs'); fireEvent.click(element); @@ -137,4 +144,22 @@ describe('OngoingTask', () => { await expect(rendered.findByText('Hide Logs')).resolves.toBeInTheDocument(); }); + + it('should have cancel and start over buttons be disabled without the proper permissions', async () => { + const mockAuthorize = jest + .fn() + .mockImplementation(async () => ({ result: AuthorizeResult.DENY })); + const permissionApi: PermissionApi = { authorize: mockAuthorize }; + const rendered = await render(permissionApi); + + const { getByTestId } = rendered; + expect(getByTestId('cancel-button')).toHaveClass('Mui-disabled'); + expect(getByTestId('start-over-button')).toHaveClass('Mui-disabled'); + + await act(async () => { + fireEvent.click(getByTestId('menu-button')); + }); + expect(getByTestId('cancel-task')).toHaveClass('Mui-disabled'); + expect(getByTestId('start-over-task')).toHaveClass('Mui-disabled'); + }); }); diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index 3e7a3b8f52..d5f6dcfb0d 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -35,6 +35,12 @@ import { TaskSteps, } from '@backstage/plugin-scaffolder-react/alpha'; import { useAsync } from '@react-hookz/web'; +import { usePermission } from '@backstage/plugin-permission-react'; +import { + taskCancelPermission, + taskReadPermission, + taskCreatePermission, +} from '@backstage/plugin-scaffolder-common/alpha'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -81,6 +87,28 @@ export const OngoingTask = (props: { const [logsVisible, setLogVisibleState] = useState(false); const [buttonBarVisible, setButtonBarVisibleState] = useState(true); + // Used dummy string value for `resourceRef` since `allowed` field will always return `false` if `resourceRef` is `undefined` + const { allowed: canCancelTask } = usePermission({ + permission: taskCancelPermission, + resourceRef: 'task', + }); + + const { allowed: canReadTask } = usePermission({ + permission: taskReadPermission, + resourceRef: 'task', + }); + + const { allowed: canCreateTask } = usePermission({ + permission: taskCreatePermission, + resourceRef: 'task', + }); + + // Cancel endpoint requires user to have both read and cancel permissions + const cancelNotAllowed = !(canReadTask && canCancelTask); + + // Start Over endpoint requires user to have both read (to grab parameters) and create (to create new task) permissions + const canStartOver = canReadTask && canCreateTask; + useEffect(() => { if (taskStream.error) { setLogVisibleState(true); @@ -192,7 +220,11 @@ export const OngoingTask = (props: {
From fa26d03a6aab6fed41dc1b08cff88397e242b31e Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 25 Apr 2024 16:43:34 -0400 Subject: [PATCH 006/118] chore: update package.json Signed-off-by: Frank Kong --- plugins/catalog/package.json | 3 ++- plugins/scaffolder/package.json | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index db2089a89e..bc4e125b97 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -87,7 +87,8 @@ "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^15.0.0", - "@testing-library/user-event": "^14.0.0" + "@testing-library/user-event": "^14.0.0", + "swr": "^2.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 32dc701a1e..5c0773bd65 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -98,6 +98,7 @@ "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/plugin-catalog": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", @@ -105,7 +106,8 @@ "@testing-library/user-event": "^14.0.0", "@types/humanize-duration": "^3.18.1", "@types/json-schema": "^7.0.9", - "msw": "^1.0.0" + "msw": "^1.0.0", + "swr": "^2.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", From bcec60fb4a46137be4ab7ecc3d07170b69c5eb5f Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 25 Apr 2024 17:18:47 -0400 Subject: [PATCH 007/118] chore: add changeset Signed-off-by: Frank Kong --- .changeset/tender-seas-listen.md | 12 ++++++++++++ .changeset/weak-gifts-occur.md | 11 +++++++++++ 2 files changed, 23 insertions(+) create mode 100644 .changeset/tender-seas-listen.md create mode 100644 .changeset/weak-gifts-occur.md diff --git a/.changeset/tender-seas-listen.md b/.changeset/tender-seas-listen.md new file mode 100644 index 0000000000..cfcea07574 --- /dev/null +++ b/.changeset/tender-seas-listen.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-catalog': patch +--- + +updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + +- `scaffolder.task.create` +- `scaffolder.task.cancel` +- `scaffolder.task.read` +- `scaffolder.action.read` diff --git a/.changeset/weak-gifts-occur.md b/.changeset/weak-gifts-occur.md new file mode 100644 index 0000000000..c1a65e7dff --- /dev/null +++ b/.changeset/weak-gifts-occur.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-common': patch +--- + +added the following new permissions to the scaffolder backend endpoints: + +- `scaffolder.task.create` +- `scaffolder.task.cancel` +- `scaffolder.task.read` +- `scaffolder.action.read` From 959ed3afb27396b9cf532e708d22bfc7a7113a3a Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 26 Apr 2024 09:07:04 -0400 Subject: [PATCH 008/118] chore: fix tsc Signed-off-by: Frank Kong --- plugins/catalog/src/components/AboutCard/AboutCard.test.tsx | 2 +- .../src/next/components/TemplateCard/TemplateCard.test.tsx | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 76d41399ba..fcea659fc6 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -831,7 +831,7 @@ describe('', () => { mockAuthorize.mockImplementation(async () => ({ result: AuthorizeResult.DENY, })); - const rendered = await renderInTestApp( + await renderInTestApp( new Map() }}> Date: Fri, 26 Apr 2024 09:30:26 -0400 Subject: [PATCH 009/118] chore: update api-reports Signed-off-by: Frank Kong --- plugins/scaffolder-backend/api-report.md | 17 ++++++++++++++--- plugins/scaffolder-common/api-report-alpha.md | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 6af1ac04a6..dc7917e825 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -36,6 +36,7 @@ import { PermissionsService } from '@backstage/backend-plugin-api'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; +import { RESOURCE_TYPE_SCAFFOLDER_TASK } from '@backstage/plugin-scaffolder-common/alpha'; import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha'; import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; import { Schema } from 'jsonschema'; @@ -478,10 +479,10 @@ export interface RouterOptions { lifecycle?: LifecycleService; // (undocumented) logger: Logger; + // Warning: (ae-forgotten-export) The symbol "ScaffolderPermissionRuleInput" needs to be exported by the entry point index.d.ts + // // (undocumented) - permissionRules?: Array< - TemplatePermissionRuleInput | ActionPermissionRuleInput - >; + permissionRules?: Array; // (undocumented) permissions?: PermissionsService; // (undocumented) @@ -575,6 +576,16 @@ export class TaskManager implements TaskContext_2 { ): Promise; } +// @public (undocumented) +export type TaskPermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule< + TemplateEntityStepV1beta3 | TemplateParametersV1beta3, + {}, + typeof RESOURCE_TYPE_SCAFFOLDER_TASK, + TParams +>; + // @public @deprecated (undocumented) export type TaskSecrets = TaskSecrets_2; diff --git a/plugins/scaffolder-common/api-report-alpha.md b/plugins/scaffolder-common/api-report-alpha.md index de9dffd1b9..3762b0b773 100644 --- a/plugins/scaffolder-common/api-report-alpha.md +++ b/plugins/scaffolder-common/api-report-alpha.md @@ -8,9 +8,15 @@ import { ResourcePermission } from '@backstage/plugin-permission-common'; // @alpha export const actionExecutePermission: ResourcePermission<'scaffolder-action'>; +// @alpha +export const actionReadPermission: ResourcePermission<'scaffolder-action'>; + // @alpha export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; +// @alpha +export const RESOURCE_TYPE_SCAFFOLDER_TASK = 'scaffolder-task'; + // @alpha export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; @@ -23,9 +29,21 @@ export const scaffolderPermissions: ( | ResourcePermission<'scaffolder-template'> )[]; +// @alpha +export const scaffolderTaskPermissions: ResourcePermission<'scaffolder-task'>[]; + // @alpha export const scaffolderTemplatePermissions: ResourcePermission<'scaffolder-template'>[]; +// @alpha +export const taskCancelPermission: ResourcePermission<'scaffolder-task'>; + +// @alpha +export const taskCreatePermission: ResourcePermission<'scaffolder-task'>; + +// @alpha +export const taskReadPermission: ResourcePermission<'scaffolder-task'>; + // @alpha export const templateParameterReadPermission: ResourcePermission<'scaffolder-template'>; From 2b959e049fd976396f0d97ee621c48deaa5565cb Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 26 Apr 2024 10:21:50 -0400 Subject: [PATCH 010/118] chore: merge dependency imports Signed-off-by: Frank Kong --- plugins/scaffolder-react/src/extensions/rjsf.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-react/src/extensions/rjsf.ts b/plugins/scaffolder-react/src/extensions/rjsf.ts index cacedad080..b81f06758f 100644 --- a/plugins/scaffolder-react/src/extensions/rjsf.ts +++ b/plugins/scaffolder-react/src/extensions/rjsf.ts @@ -14,7 +14,14 @@ * limitations under the License. */ -import { ComponentType, ElementType, FormEvent, ReactNode, Ref } from 'react'; +import { + ComponentType, + ElementType, + FormEvent, + HTMLAttributes, + ReactNode, + Ref, +} from 'react'; import { ErrorSchema, FormContextType, @@ -32,7 +39,6 @@ import { Experimental_DefaultFormStateBehavior, ErrorTransformer, } from '@rjsf/utils'; -import { HTMLAttributes } from 'react'; import Form, { IChangeEvent } from '@rjsf/core'; /** From e4043011e7936cf6ea030850754ae769875f8143 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 26 Apr 2024 12:09:24 -0400 Subject: [PATCH 011/118] chore(scaffolder): fix api-report Signed-off-by: Frank Kong --- plugins/scaffolder-backend/api-report.md | 8 ++++++-- plugins/scaffolder-backend/src/service/router.ts | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index dc7917e825..e99ac56bee 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -479,8 +479,6 @@ export interface RouterOptions { lifecycle?: LifecycleService; // (undocumented) logger: Logger; - // Warning: (ae-forgotten-export) The symbol "ScaffolderPermissionRuleInput" needs to be exported by the entry point index.d.ts - // // (undocumented) permissionRules?: Array; // (undocumented) @@ -501,6 +499,12 @@ export type RunCommandOptions = ExecuteShellCommandOptions; // @public @deprecated export const ScaffolderEntitiesProcessor: typeof ScaffolderEntitiesProcessor_2; +// @public (undocumented) +export type ScaffolderPermissionRuleInput = + | TemplatePermissionRuleInput + | ActionPermissionRuleInput + | TaskPermissionRuleInput; + // @public @deprecated export type SerializedTask = SerializedTask_2; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 3fd1e14fa1..228f7bed12 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -104,7 +104,11 @@ import { } from '@backstage/plugin-auth-node'; import { InternalTaskSecrets } from '../scaffolder/tasks/types'; -type ScaffolderPermissionRuleInput = +/** + * + * @public + */ +export type ScaffolderPermissionRuleInput = | TemplatePermissionRuleInput | ActionPermissionRuleInput | TaskPermissionRuleInput; From 3078ff09b7654cafde6c59062da6beba37353a70 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 26 Apr 2024 12:31:08 -0400 Subject: [PATCH 012/118] chore: update yarn.lock Signed-off-by: Frank Kong --- yarn.lock | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..55cd380654 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5684,6 +5684,7 @@ __metadata: lodash: ^4.17.21 pluralize: ^8.0.0 react-use: ^17.2.4 + swr: ^2.0.0 zen-observable: ^0.10.0 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 @@ -6877,6 +6878,8 @@ __metadata: "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" + "@backstage/plugin-permission-react": "workspace:^" "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" @@ -6907,6 +6910,7 @@ __metadata: luxon: ^3.0.0 qs: ^6.9.4 react-use: ^17.2.4 + swr: ^2.0.0 use-immer: ^0.9.0 zen-observable: ^0.10.0 zod: ^3.22.4 @@ -6937,6 +6941,7 @@ __metadata: "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/plugin-scaffolder-react": "workspace:^" @@ -6973,6 +6978,7 @@ __metadata: msw: ^1.0.0 qs: ^6.9.4 react-use: ^17.2.4 + swr: ^2.0.0 yaml: ^2.0.0 zen-observable: ^0.10.0 zod: ^3.22.4 From faa86f3981845b86f1b90ce24b3fcf15f0825787 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 2 May 2024 09:57:49 +0200 Subject: [PATCH 013/118] Register the to the DevApp fixing the issue to launch locally the plugin. Add a new section to the plugin scaffolder README. #23684 Signed-off-by: cmoulliard --- .changeset/few-dodos-cheer.md | 5 +++++ plugins/scaffolder/README.md | 9 +++++++++ plugins/scaffolder/dev/index.tsx | 18 ++++++------------ 3 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 .changeset/few-dodos-cheer.md diff --git a/.changeset/few-dodos-cheer.md b/.changeset/few-dodos-cheer.md new file mode 100644 index 0000000000..a80147e112 --- /dev/null +++ b/.changeset/few-dodos-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Register the `catalogPlugin` to the DevApp fixing the issue to launch locally the plugin diff --git a/plugins/scaffolder/README.md b/plugins/scaffolder/README.md index a6af441d62..07111a33e5 100644 --- a/plugins/scaffolder/README.md +++ b/plugins/scaffolder/README.md @@ -121,6 +121,15 @@ export const apis: AnyApiFactory[] = [ This replaces the default implementation of the `scaffolderApiRef`. +### Local development + +When you develop a new template, action or new ``, then we recommend +to launch the plugin locally using the `createDevApp` of the `./dev/index.tsx` file for testing/Debugging purposes + +To play with it, open a terminal and run the command: `yarn start` within the `./plugins/scaffolder` folder + +**NOTE:** Don't forget to open a second terminal and to launch the backend or [backend-next](../../docs/backend-system/index.md) there, using `yarn start` and to specify the locations of the templates to play with ! + ## Links - [scaffolder-backend](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend) diff --git a/plugins/scaffolder/dev/index.tsx b/plugins/scaffolder/dev/index.tsx index e4425b04bf..2980fe90fc 100644 --- a/plugins/scaffolder/dev/index.tsx +++ b/plugins/scaffolder/dev/index.tsx @@ -14,11 +14,9 @@ * limitations under the License. */ -import { CatalogClient } from '@backstage/catalog-client'; import { createDevApp } from '@backstage/dev-utils'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { - catalogApiRef, starredEntitiesApiRef, MockStarredEntitiesApi, } from '@backstage/plugin-catalog-react'; @@ -30,18 +28,10 @@ import { fetchApiRef, identityApiRef, } from '@backstage/core-plugin-api'; -import { CatalogEntityPage } from '@backstage/plugin-catalog'; +import { CatalogEntityPage, catalogPlugin } from '@backstage/plugin-catalog'; createDevApp() - .addPage({ - path: '/catalog/:kind/:namespace/:name', - element: , - }) - .registerApi({ - api: catalogApiRef, - deps: { discoveryApi: discoveryApiRef }, - factory: ({ discoveryApi }) => new CatalogClient({ discoveryApi }), - }) + .registerPlugin(catalogPlugin) .registerApi({ api: starredEntitiesApiRef, deps: {}, @@ -63,6 +53,10 @@ createDevApp() identityApi, }), }) + .addPage({ + path: '/catalog/:kind/:namespace/:name', + element: , + }) .addPage({ path: '/create', title: 'Create', From 83643ef0919f5dadcdb4ea7ea62bcf593dc04698 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 10:16:25 -0400 Subject: [PATCH 014/118] chore: add util to perform basic permission check Signed-off-by: Frank Kong --- .../scaffolder-backend/src/service/router.ts | 147 +++++------------- .../src/util/checkPermissions.ts | 53 +++++++ 2 files changed, 95 insertions(+), 105 deletions(-) create mode 100644 plugins/scaffolder-backend/src/util/checkPermissions.ts diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 228f7bed12..3c98adc3f4 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -103,6 +103,7 @@ import { IdentityApiGetIdentityRequest, } from '@backstage/plugin-auth-node'; import { InternalTaskSecrets } from '../scaffolder/tasks/types'; +import { checkPermission } from '../util/checkPermissions'; /** * @@ -492,19 +493,11 @@ export async function createRouter( ) .get('/v2/actions', async (req, res) => { const credentials = await httpAuth.credentials(req); - - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: actionReadPermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } + await checkPermission({ + credentials, + permissions: [actionReadPermission], + permissionService: permissions, + }); const actionsList = actionRegistry.list().map(action => { return { id: action.id, @@ -522,18 +515,11 @@ export async function createRouter( }); const credentials = await httpAuth.credentials(req); - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskCreatePermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } + await checkPermission({ + credentials, + permissions: [taskCreatePermission], + permissionService: permissions, + }); const { token } = await auth.getPluginRequestToken({ onBehalfOf: credentials, @@ -612,22 +598,13 @@ export async function createRouter( }) .get('/v2/tasks', async (req, res) => { const credentials = await httpAuth.credentials(req); - - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskReadPermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } + await checkPermission({ + credentials, + permissions: [taskReadPermission], + permissionService: permissions, + }); const [userEntityRef] = [req.query.createdBy].flat(); - if ( typeof userEntityRef !== 'string' && typeof userEntityRef !== 'undefined' @@ -649,19 +626,11 @@ export async function createRouter( }) .get('/v2/tasks/:taskId', async (req, res) => { const credentials = await httpAuth.credentials(req); - - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskReadPermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } + await checkPermission({ + credentials, + permissions: [taskReadPermission], + permissionService: permissions, + }); const { taskId } = req.params; const task = await taskBroker.get(taskId); @@ -674,22 +643,12 @@ export async function createRouter( }) .post('/v2/tasks/:taskId/cancel', async (req, res) => { const credentials = await httpAuth.credentials(req); - - if (permissions) { - const authorizationResponses = await permissions.authorizeConditional( - [ - { permission: taskCancelPermission }, - { permission: taskReadPermission }, - ], - { credentials: credentials }, - ); - // Requires both read and cancel permissions - for (const response of authorizationResponses) { - if (response.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } - } + // Requires both read and cancel permissions + await checkPermission({ + credentials, + permissions: [taskCancelPermission, taskReadPermission], + permissionService: permissions, + }); const { taskId } = req.params; await taskBroker.cancel?.(taskId); @@ -697,19 +656,12 @@ export async function createRouter( }) .get('/v2/tasks/:taskId/eventstream', async (req, res) => { const credentials = await httpAuth.credentials(req); + await checkPermission({ + credentials, + permissions: [taskReadPermission], + permissionService: permissions, + }); - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskReadPermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } const { taskId } = req.params; const after = req.query.after !== undefined ? Number(req.query.after) : undefined; @@ -759,19 +711,12 @@ export async function createRouter( }) .get('/v2/tasks/:taskId/events', async (req, res) => { const credentials = await httpAuth.credentials(req); + await checkPermission({ + credentials, + permissions: [taskReadPermission], + permissionService: permissions, + }); - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskReadPermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } const { taskId } = req.params; const after = Number(req.query.after) || undefined; @@ -803,19 +748,11 @@ export async function createRouter( }) .post('/v2/dry-run', async (req, res) => { const credentials = await httpAuth.credentials(req); - - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskCreatePermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } + await checkPermission({ + credentials, + permissions: [taskCreatePermission], + permissionService: permissions, + }); const bodySchema = z.object({ template: z.unknown(), diff --git a/plugins/scaffolder-backend/src/util/checkPermissions.ts b/plugins/scaffolder-backend/src/util/checkPermissions.ts new file mode 100644 index 0000000000..42d841ce2b --- /dev/null +++ b/plugins/scaffolder-backend/src/util/checkPermissions.ts @@ -0,0 +1,53 @@ +/* + * 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. + */ +import { + BackstageCredentials, + PermissionsService, +} from '@backstage/backend-plugin-api'; +import { NotAllowedError } from '@backstage/errors'; +import { + AuthorizeResult, + ResourcePermission, +} from '@backstage/plugin-permission-common'; + +export type checkPermissionOptions = { + credentials: BackstageCredentials; + permissions: ResourcePermission[]; + permissionService?: PermissionsService; +}; + +/** + * Does a basic check on permissions. Throws 403 error if any permission responds with AuthorizeResult.DENY + * @public + */ +export async function checkPermission(options: checkPermissionOptions) { + const { permissions, permissionService, credentials } = options; + if (permissionService) { + const permissionRequest = permissions.map(resourcePermission => ({ + permission: resourcePermission, + })); + const authorizationResponses = await permissionService.authorizeConditional( + permissionRequest, + { credentials: credentials }, + ); + + for (const response of authorizationResponses) { + if (response.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } + } +} From a2a6a826d82e4fb578b8e0d1f9b662ba42bbcfed Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 10:29:44 -0400 Subject: [PATCH 015/118] chore: remove unused imports Signed-off-by: Frank Kong --- plugins/scaffolder-backend/src/service/router.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 3c98adc3f4..0d55d20c9e 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -30,12 +30,7 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; -import { - InputError, - NotAllowedError, - NotFoundError, - stringifyError, -} from '@backstage/errors'; +import { InputError, NotFoundError, stringifyError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { @@ -79,10 +74,7 @@ import { import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; import { findTemplate, getEntityBaseUrl, getWorkingDirectory } from './helpers'; -import { - AuthorizeResult, - PermissionRuleParams, -} from '@backstage/plugin-permission-common'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { createConditionAuthorizer, createPermissionIntegrationRouter, From e5b903599f676edda9ac4d51419a5a931bd3a8d0 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 12:07:30 -0400 Subject: [PATCH 016/118] chore: update unit tests Signed-off-by: Frank Kong --- plugins/scaffolder-backend/src/service/router.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 2118f46b22..4443b28d09 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -895,6 +895,11 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ it('filters steps that the user is not authorized to see', async () => { jest .spyOn(permissionApi, 'authorizeConditional') + .mockImplementationOnce(async () => [ + { + result: AuthorizeResult.ALLOW, + }, + ]) .mockImplementation(async () => [ { result: AuthorizeResult.ALLOW, From ea7cb44de53845959c6f9acec2f1a4d21b66404f Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 22:36:30 -0400 Subject: [PATCH 017/118] chore: apply suggestions Signed-off-by: Frank Kong --- plugins/catalog/package.json | 2 +- .../src/components/AboutCard/AboutCard.tsx | 3 +- .../tasks/NunjucksWorkflowRunner.ts | 2 +- .../scaffolder-backend/src/service/router.ts | 38 ++++++++++++------- .../src/util/checkPermissions.ts | 6 +-- plugins/scaffolder-common/src/permissions.ts | 18 +-------- .../components/TemplateCard/TemplateCard.tsx | 1 - .../components/OngoingTask/ContextMenu.tsx | 9 +---- .../components/OngoingTask/OngoingTask.tsx | 8 +--- yarn.lock | 4 +- 10 files changed, 36 insertions(+), 55 deletions(-) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index da973aadb5..d22d9778e6 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -89,7 +89,7 @@ "@testing-library/react": "^15.0.0", "@testing-library/user-event": "^14.0.0", "@types/pluralize": "^0.0.33", - "swr": "^2.0.0" + "swr": "^2.2.5" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index b63666f80c..ee6147c54e 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -116,10 +116,9 @@ export function AboutCard(props: AboutCardProps) { const { allowed: canRefresh } = useEntityPermission( catalogEntityRefreshPermission, ); - const { kind, name, namespace } = entity.metadata; + const { allowed: canCreateTemplateTask } = usePermission({ permission: taskCreatePermission, - resourceRef: `${kind}:${namespace}/${name}`, }); const entitySourceLocation = getEntitySourceLocation( diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 5d24ac7cc7..99eb7c7bb5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -31,6 +31,7 @@ import { SecureTemplateRenderer, } from '../../lib/templating/SecureTemplater'; import { + TaskRecovery, TaskSpec, TaskSpecV1beta3, TaskStep, @@ -52,7 +53,6 @@ import { } from '@backstage/plugin-permission-common'; import { scaffolderActionRules } from '../../service/rules'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; -import { TaskRecovery } from '@backstage/plugin-scaffolder-common'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { loggerToWinstonLogger } from '@backstage/backend-common'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 0d55d20c9e..287d058030 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -30,7 +30,12 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; -import { InputError, NotFoundError, stringifyError } from '@backstage/errors'; +import { + InputError, + NotAllowedError, + NotFoundError, + stringifyError, +} from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { @@ -43,7 +48,6 @@ import { import { RESOURCE_TYPE_SCAFFOLDER_ACTION, RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, - RESOURCE_TYPE_SCAFFOLDER_TASK, scaffolderActionPermissions, scaffolderTemplatePermissions, taskCancelPermission, @@ -74,7 +78,10 @@ import { import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; import { findTemplate, getEntityBaseUrl, getWorkingDirectory } from './helpers'; -import { PermissionRuleParams } from '@backstage/plugin-permission-common'; +import { + AuthorizeResult, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; import { createConditionAuthorizer, createPermissionIntegrationRouter, @@ -97,11 +104,7 @@ import { import { InternalTaskSecrets } from '../scaffolder/tasks/types'; import { checkPermission } from '../util/checkPermissions'; -/** - * - * @public - */ -export type ScaffolderPermissionRuleInput = +type ScaffolderPermissionRuleInput = | TemplatePermissionRuleInput | ActionPermissionRuleInput | TaskPermissionRuleInput; @@ -440,7 +443,7 @@ export async function createRouter( rules: actionRules, }, { - resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, + resourceType: 'basic', permissions: scaffolderTaskPermissions, rules: taskRules, }, @@ -485,11 +488,18 @@ export async function createRouter( ) .get('/v2/actions', async (req, res) => { const credentials = await httpAuth.credentials(req); - await checkPermission({ - credentials, - permissions: [actionReadPermission], - permissionService: permissions, - }); + if (permissions) { + const authorizationResponse = ( + await permissions.authorizeConditional( + [{ permission: actionReadPermission }], + { credentials: credentials }, + ) + )[0]; + if (authorizationResponse.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } + const actionsList = actionRegistry.list().map(action => { return { id: action.id, diff --git a/plugins/scaffolder-backend/src/util/checkPermissions.ts b/plugins/scaffolder-backend/src/util/checkPermissions.ts index 42d841ce2b..40aa673b3f 100644 --- a/plugins/scaffolder-backend/src/util/checkPermissions.ts +++ b/plugins/scaffolder-backend/src/util/checkPermissions.ts @@ -20,12 +20,12 @@ import { import { NotAllowedError } from '@backstage/errors'; import { AuthorizeResult, - ResourcePermission, + BasicPermission, } from '@backstage/plugin-permission-common'; export type checkPermissionOptions = { credentials: BackstageCredentials; - permissions: ResourcePermission[]; + permissions: BasicPermission[]; permissionService?: PermissionsService; }; @@ -39,7 +39,7 @@ export async function checkPermission(options: checkPermissionOptions) { const permissionRequest = permissions.map(resourcePermission => ({ permission: resourcePermission, })); - const authorizationResponses = await permissionService.authorizeConditional( + const authorizationResponses = await permissionService.authorize( permissionRequest, { credentials: credentials }, ); diff --git a/plugins/scaffolder-common/src/permissions.ts b/plugins/scaffolder-common/src/permissions.ts index 4a4137875a..6bd7e130ec 100644 --- a/plugins/scaffolder-common/src/permissions.ts +++ b/plugins/scaffolder-common/src/permissions.ts @@ -30,13 +30,6 @@ export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; */ export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; -/** - * Permission resource type which corresponds to a scaffolder task. - * - * @alpha - */ -export const RESOURCE_TYPE_SCAFFOLDER_TASK = 'scaffolder-task'; - /** * This permission is used to authorize actions that involve executing * an action from a template. @@ -49,6 +42,7 @@ export const actionExecutePermission = createPermission({ resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, }); +// TODO: Figure out whether to convert this to a basic permission or remove it completely since the current rules aren't applicable to this permission /** * This permission is used to authorize actions that involve access the action registry * @@ -101,8 +95,6 @@ export const templateStepReadPermission = createPermission({ * This permission is used to authorize actions that involve reading one or more tasks in the scaffolder, * and reading logs of tasks * - * Task cancellation would also require this permission. - * * @alpha */ export const taskReadPermission = createPermission({ @@ -110,7 +102,6 @@ export const taskReadPermission = createPermission({ attributes: { action: 'read', }, - resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, }); /** @@ -123,20 +114,16 @@ export const taskCreatePermission = createPermission({ attributes: { action: 'create', }, - resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, }); /** - * This permission us used to authorize actions that involve the cancellation of tasks in the scaffolder. - * - * This will require the `scaffolder.task.read` permission to be authorized. + * This permission is used to authorize actions that involve the cancellation of tasks in the scaffolder. * * @alpha */ export const taskCancelPermission = createPermission({ name: 'scaffolder.task.cancel', attributes: {}, - resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, }); /** @@ -144,7 +131,6 @@ export const taskCancelPermission = createPermission({ * @alpha */ export const scaffolderPermissions = [ - actionExecutePermission, templateParameterReadPermission, templateStepReadPermission, ]; diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx index 253d588862..5d2c8aa7fe 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx @@ -112,7 +112,6 @@ export const TemplateCard = (props: TemplateCardProps) => { const { allowed: canCreateTask } = usePermission({ permission: taskCreatePermission, - resourceRef: 'task', }); const handleChoose = useCallback(() => { analytics.captureEvent('click', `Template has been opened`); diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index 1a5cccf142..f96f83fd2f 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -77,25 +77,18 @@ export const ContextMenu = (props: ContextMenuProps) => { } }); - // Used dummy string value for `resourceRef` since `allowed` field will always return `false` if `resourceRef` is `undefined` const { allowed: canCancelTask } = usePermission({ permission: taskCancelPermission, - resourceRef: 'task', }); const { allowed: canReadTask } = usePermission({ permission: taskReadPermission, - resourceRef: 'task', }); const { allowed: canCreateTask } = usePermission({ permission: taskCreatePermission, - resourceRef: 'task', }); - // Cancel endpoint requires user to have both read and cancel permissions - const cancelNotAllowed = !(canReadTask && canCancelTask); - // Start Over endpoint requires user to have both read (to grab parameters) and create (to create new task) permissions const canStartOver = canReadTask && canCreateTask; @@ -150,7 +143,7 @@ export const ContextMenu = (props: ContextMenuProps) => { disabled={ !cancelEnabled || cancelStatus !== 'not-executed' || - cancelNotAllowed + !canCancelTask } data-testid="cancel-task" > diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index b7af4b6618..f4099028ba 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -91,22 +91,16 @@ export const OngoingTask = (props: { // Used dummy string value for `resourceRef` since `allowed` field will always return `false` if `resourceRef` is `undefined` const { allowed: canCancelTask } = usePermission({ permission: taskCancelPermission, - resourceRef: 'task', }); const { allowed: canReadTask } = usePermission({ permission: taskReadPermission, - resourceRef: 'task', }); const { allowed: canCreateTask } = usePermission({ permission: taskCreatePermission, - resourceRef: 'task', }); - // Cancel endpoint requires user to have both read and cancel permissions - const cancelNotAllowed = !(canReadTask && canCancelTask); - // Start Over endpoint requires user to have both read (to grab parameters) and create (to create new task) permissions const canStartOver = canReadTask && canCreateTask; @@ -228,7 +222,7 @@ export const OngoingTask = (props: { disabled={ !cancelEnabled || cancelStatus !== 'not-executed' || - cancelNotAllowed + !canCancelTask } onClick={triggerCancel} data-testid="cancel-button" diff --git a/yarn.lock b/yarn.lock index bf16ce2937..f5d34f9c95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5684,7 +5684,7 @@ __metadata: lodash: ^4.17.21 pluralize: ^8.0.0 react-use: ^17.2.4 - swr: ^2.0.0 + swr: ^2.2.5 zen-observable: ^0.10.0 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 @@ -39520,7 +39520,7 @@ __metadata: languageName: node linkType: hard -"swr@npm:^2.0.0": +"swr@npm:^2.0.0, swr@npm:^2.2.5": version: 2.2.5 resolution: "swr@npm:2.2.5" dependencies: From b75d78761cfa1876346de726ed5258da08c88fbb Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 22:53:12 -0400 Subject: [PATCH 018/118] chore(scaffolder-backend): update unit tests for router Signed-off-by: Frank Kong --- .../scaffolder-backend/src/service/router.test.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 4443b28d09..c129859c49 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -235,6 +235,11 @@ describe('createRouter', () => { result: AuthorizeResult.ALLOW, }, ]); + jest.spyOn(permissionApi, 'authorize').mockImplementation(async () => [ + { + result: AuthorizeResult.ALLOW, + }, + ]); }); afterEach(() => { @@ -741,6 +746,11 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ result: AuthorizeResult.ALLOW, }, ]); + jest.spyOn(permissionApi, 'authorize').mockImplementation(async () => [ + { + result: AuthorizeResult.ALLOW, + }, + ]); }); afterEach(() => { @@ -895,11 +905,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ it('filters steps that the user is not authorized to see', async () => { jest .spyOn(permissionApi, 'authorizeConditional') - .mockImplementationOnce(async () => [ - { - result: AuthorizeResult.ALLOW, - }, - ]) .mockImplementation(async () => [ { result: AuthorizeResult.ALLOW, From e01a2e93caeaec35b95afa50189df1b2548d402c Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 23:16:04 -0400 Subject: [PATCH 019/118] chore: fix tsc errors and update api-report Signed-off-by: Frank Kong --- plugins/scaffolder-backend/api-report.md | 14 +-------- .../scaffolder-backend/src/service/router.ts | 30 +++++-------------- plugins/scaffolder-common/api-report-alpha.md | 17 ++++------- 3 files changed, 14 insertions(+), 47 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index e99ac56bee..e6738497fe 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -36,7 +36,6 @@ import { PermissionsService } from '@backstage/backend-plugin-api'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; -import { RESOURCE_TYPE_SCAFFOLDER_TASK } from '@backstage/plugin-scaffolder-common/alpha'; import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha'; import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; import { Schema } from 'jsonschema'; @@ -502,8 +501,7 @@ export const ScaffolderEntitiesProcessor: typeof ScaffolderEntitiesProcessor_2; // @public (undocumented) export type ScaffolderPermissionRuleInput = | TemplatePermissionRuleInput - | ActionPermissionRuleInput - | TaskPermissionRuleInput; + | ActionPermissionRuleInput; // @public @deprecated export type SerializedTask = SerializedTask_2; @@ -580,16 +578,6 @@ export class TaskManager implements TaskContext_2 { ): Promise; } -// @public (undocumented) -export type TaskPermissionRuleInput< - TParams extends PermissionRuleParams = PermissionRuleParams, -> = PermissionRule< - TemplateEntityStepV1beta3 | TemplateParametersV1beta3, - {}, - typeof RESOURCE_TYPE_SCAFFOLDER_TASK, - TParams ->; - // @public @deprecated (undocumented) export type TaskSecrets = TaskSecrets_2; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 287d058030..ff1796e71f 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -104,10 +104,13 @@ import { import { InternalTaskSecrets } from '../scaffolder/tasks/types'; import { checkPermission } from '../util/checkPermissions'; -type ScaffolderPermissionRuleInput = +/** + * + * @public + */ +export type ScaffolderPermissionRuleInput = | TemplatePermissionRuleInput - | ActionPermissionRuleInput - | TaskPermissionRuleInput; + | ActionPermissionRuleInput; /** * @@ -145,23 +148,6 @@ function isActionPermissionRuleInput( return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION; } -/** - * - * @public - */ -export type TaskPermissionRuleInput< - TParams extends PermissionRuleParams = PermissionRuleParams, -> = PermissionRule< - TemplateEntityStepV1beta3 | TemplateParametersV1beta3, - {}, - typeof RESOURCE_TYPE_SCAFFOLDER_TASK, - TParams ->; -function isTaskPermissionRuleInput( - permissionRule: ScaffolderPermissionRuleInput, -): permissionRule is TaskPermissionRuleInput { - return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TASK; -} /** * RouterOptions * @@ -418,14 +404,12 @@ export async function createRouter( const actionRules: ActionPermissionRuleInput[] = Object.values( scaffolderActionRules, ); - const taskRules: TaskPermissionRuleInput[] = []; if (permissionRules) { templateRules.push( ...permissionRules.filter(isTemplatePermissionRuleInput), ); actionRules.push(...permissionRules.filter(isActionPermissionRuleInput)); - taskRules.push(...permissionRules.filter(isTaskPermissionRuleInput)); } const isAuthorized = createConditionAuthorizer(Object.values(templateRules)); @@ -445,7 +429,7 @@ export async function createRouter( { resourceType: 'basic', permissions: scaffolderTaskPermissions, - rules: taskRules, + rules: [], }, ], }); diff --git a/plugins/scaffolder-common/api-report-alpha.md b/plugins/scaffolder-common/api-report-alpha.md index 3762b0b773..a06f3123e7 100644 --- a/plugins/scaffolder-common/api-report-alpha.md +++ b/plugins/scaffolder-common/api-report-alpha.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BasicPermission } from '@backstage/plugin-permission-common'; import { ResourcePermission } from '@backstage/plugin-permission-common'; // @alpha @@ -14,9 +15,6 @@ export const actionReadPermission: ResourcePermission<'scaffolder-action'>; // @alpha export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; -// @alpha -export const RESOURCE_TYPE_SCAFFOLDER_TASK = 'scaffolder-task'; - // @alpha export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; @@ -24,25 +22,22 @@ export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; export const scaffolderActionPermissions: ResourcePermission<'scaffolder-action'>[]; // @alpha -export const scaffolderPermissions: ( - | ResourcePermission<'scaffolder-action'> - | ResourcePermission<'scaffolder-template'> -)[]; +export const scaffolderPermissions: ResourcePermission<'scaffolder-template'>[]; // @alpha -export const scaffolderTaskPermissions: ResourcePermission<'scaffolder-task'>[]; +export const scaffolderTaskPermissions: BasicPermission[]; // @alpha export const scaffolderTemplatePermissions: ResourcePermission<'scaffolder-template'>[]; // @alpha -export const taskCancelPermission: ResourcePermission<'scaffolder-task'>; +export const taskCancelPermission: BasicPermission; // @alpha -export const taskCreatePermission: ResourcePermission<'scaffolder-task'>; +export const taskCreatePermission: BasicPermission; // @alpha -export const taskReadPermission: ResourcePermission<'scaffolder-task'>; +export const taskReadPermission: BasicPermission; // @alpha export const templateParameterReadPermission: ResourcePermission<'scaffolder-template'>; From 84c87637f3b98e3f0679df780e47c92f56c59dd5 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 3 May 2024 10:39:47 -0400 Subject: [PATCH 020/118] chore(scaffolder-backend): revert addition of internal type Signed-off-by: Frank Kong --- plugins/scaffolder-backend/api-report.md | 9 +++------ plugins/scaffolder-backend/src/service/router.ts | 16 +++++----------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index e6738497fe..6af1ac04a6 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -479,7 +479,9 @@ export interface RouterOptions { // (undocumented) logger: Logger; // (undocumented) - permissionRules?: Array; + permissionRules?: Array< + TemplatePermissionRuleInput | ActionPermissionRuleInput + >; // (undocumented) permissions?: PermissionsService; // (undocumented) @@ -498,11 +500,6 @@ export type RunCommandOptions = ExecuteShellCommandOptions; // @public @deprecated export const ScaffolderEntitiesProcessor: typeof ScaffolderEntitiesProcessor_2; -// @public (undocumented) -export type ScaffolderPermissionRuleInput = - | TemplatePermissionRuleInput - | ActionPermissionRuleInput; - // @public @deprecated export type SerializedTask = SerializedTask_2; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index ff1796e71f..759c5ca8d7 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -104,14 +104,6 @@ import { import { InternalTaskSecrets } from '../scaffolder/tasks/types'; import { checkPermission } from '../util/checkPermissions'; -/** - * - * @public - */ -export type ScaffolderPermissionRuleInput = - | TemplatePermissionRuleInput - | ActionPermissionRuleInput; - /** * * @public @@ -125,7 +117,7 @@ export type TemplatePermissionRuleInput< TParams >; function isTemplatePermissionRuleInput( - permissionRule: ScaffolderPermissionRuleInput, + permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, ): permissionRule is TemplatePermissionRuleInput { return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TEMPLATE; } @@ -143,7 +135,7 @@ export type ActionPermissionRuleInput< TParams >; function isActionPermissionRuleInput( - permissionRule: ScaffolderPermissionRuleInput, + permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, ): permissionRule is ActionPermissionRuleInput { return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION; } @@ -176,7 +168,9 @@ export interface RouterOptions { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; permissions?: PermissionsService; - permissionRules?: Array; + permissionRules?: Array< + TemplatePermissionRuleInput | ActionPermissionRuleInput + >; auth?: AuthService; httpAuth?: HttpAuthService; identity?: IdentityApi; From 2a3676b8b64a2be49aa2b8eb781d7cf5ee8a700b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 15:28:34 +0000 Subject: [PATCH 021/118] fix(deps): update dependency @keyv/redis to v2.8.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 89d4b0ee45..453c15a661 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9974,11 +9974,11 @@ __metadata: linkType: hard "@keyv/redis@npm:^2.5.3": - version: 2.8.4 - resolution: "@keyv/redis@npm:2.8.4" + version: 2.8.5 + resolution: "@keyv/redis@npm:2.8.5" dependencies: - ioredis: ^5.3.2 - checksum: 088fb439dc900d6c848c187a0a3218f8c80e3d5df0ec94995d2245b701d59c9d99d4ccc2b48b12682e20d1c0653ababc2f7a51a04737c4df539f4dac82eca87b + ioredis: ^5.4.1 + checksum: 87ffec61d31fa9de128ba3e5a7b616535ddbdaa4d92cbc9e1a9fab143adf967135e9cca16e192e8f52cc1ba00ed2a7f10eca9944d7550385530dab95333e81ef languageName: node linkType: hard @@ -27145,9 +27145,9 @@ __metadata: languageName: node linkType: hard -"ioredis@npm:^5.3.2": - version: 5.3.2 - resolution: "ioredis@npm:5.3.2" +"ioredis@npm:^5.4.1": + version: 5.4.1 + resolution: "ioredis@npm:5.4.1" dependencies: "@ioredis/commands": ^1.1.1 cluster-key-slot: ^1.1.0 @@ -27158,7 +27158,7 @@ __metadata: redis-errors: ^1.2.0 redis-parser: ^3.0.0 standard-as-callback: ^2.1.0 - checksum: 9a23559133e862a768778301efb68ae8c2af3c33562174b54a4c2d6574b976e85c75a4c34857991af733e35c48faf4c356e7daa8fb0a3543d85ff1768c8754bc + checksum: 92210294f75800febe7544c27b07e4892480172363b11971aa575be5b68f023bfed4bc858abc9792230c153aa80409047a358f174062c14d17536aa4499fe10b languageName: node linkType: hard From 2ba6e52f40ecc2b8b12bc1cd4505b903be1b2074 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Tue, 7 May 2024 12:02:17 -0400 Subject: [PATCH 022/118] chore: remove action read scaffolder permission Signed-off-by: Frank Kong --- .../scaffolder-backend/src/service/router.ts | 34 ++--------------- plugins/scaffolder-common/src/permissions.ts | 37 ++++++------------- 2 files changed, 15 insertions(+), 56 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 759c5ca8d7..384b9f5340 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -30,12 +30,7 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; -import { - InputError, - NotAllowedError, - NotFoundError, - stringifyError, -} from '@backstage/errors'; +import { InputError, NotFoundError, stringifyError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { @@ -56,7 +51,6 @@ import { templateParameterReadPermission, templateStepReadPermission, scaffolderTaskPermissions, - actionReadPermission, } from '@backstage/plugin-scaffolder-common/alpha'; import express from 'express'; import Router from 'express-promise-router'; @@ -78,10 +72,7 @@ import { import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; import { findTemplate, getEntityBaseUrl, getWorkingDirectory } from './helpers'; -import { - AuthorizeResult, - PermissionRuleParams, -} from '@backstage/plugin-permission-common'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { createConditionAuthorizer, createPermissionIntegrationRouter, @@ -420,12 +411,8 @@ export async function createRouter( permissions: scaffolderActionPermissions, rules: actionRules, }, - { - resourceType: 'basic', - permissions: scaffolderTaskPermissions, - rules: [], - }, ], + permissions: scaffolderTaskPermissions, }); router.use(permissionIntegrationRouter); @@ -464,20 +451,7 @@ export async function createRouter( }); }, ) - .get('/v2/actions', async (req, res) => { - const credentials = await httpAuth.credentials(req); - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: actionReadPermission }], - { credentials: credentials }, - ) - )[0]; - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } - + .get('/v2/actions', async (_req, res) => { const actionsList = actionRegistry.list().map(action => { return { id: action.id, diff --git a/plugins/scaffolder-common/src/permissions.ts b/plugins/scaffolder-common/src/permissions.ts index 6bd7e130ec..c441b48d5d 100644 --- a/plugins/scaffolder-common/src/permissions.ts +++ b/plugins/scaffolder-common/src/permissions.ts @@ -42,19 +42,6 @@ export const actionExecutePermission = createPermission({ resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, }); -// TODO: Figure out whether to convert this to a basic permission or remove it completely since the current rules aren't applicable to this permission -/** - * This permission is used to authorize actions that involve access the action registry - * - * @alpha - */ -export const actionReadPermission = createPermission({ - name: 'scaffolder.action.read', - attributes: { - action: 'read', - }, - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, -}); /** * This permission is used to authorize actions that involve reading * one or more parameters from a template. @@ -126,15 +113,6 @@ export const taskCancelPermission = createPermission({ attributes: {}, }); -/** - * List of all the scaffolder permissions - * @alpha - */ -export const scaffolderPermissions = [ - templateParameterReadPermission, - templateStepReadPermission, -]; - /** * List of the scaffolder permissions that are associated with template steps and parameters. * @alpha @@ -148,10 +126,7 @@ export const scaffolderTemplatePermissions = [ * List of the scaffolder permissions that are associated with scaffolder actions. * @alpha */ -export const scaffolderActionPermissions = [ - actionExecutePermission, - actionReadPermission, -]; +export const scaffolderActionPermissions = [actionExecutePermission]; /** * List of the scaffolder permissions that are associated with scaffolder tasks. @@ -162,3 +137,13 @@ export const scaffolderTaskPermissions = [ taskCreatePermission, taskReadPermission, ]; + +/** + * List of all the scaffolder permissions + * @alpha + */ +export const scaffolderPermissions = [ + ...scaffolderTemplatePermissions, + ...scaffolderActionPermissions, + ...scaffolderTaskPermissions, +]; From a1735a9f112323453ccbd2aeda849f47ce84119f Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Tue, 7 May 2024 15:46:41 -0400 Subject: [PATCH 023/118] chore(scaffolder-backend): update api-report Signed-off-by: Frank Kong --- plugins/scaffolder-common/api-report-alpha.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-common/api-report-alpha.md b/plugins/scaffolder-common/api-report-alpha.md index a06f3123e7..36b6a7cdb0 100644 --- a/plugins/scaffolder-common/api-report-alpha.md +++ b/plugins/scaffolder-common/api-report-alpha.md @@ -9,9 +9,6 @@ import { ResourcePermission } from '@backstage/plugin-permission-common'; // @alpha export const actionExecutePermission: ResourcePermission<'scaffolder-action'>; -// @alpha -export const actionReadPermission: ResourcePermission<'scaffolder-action'>; - // @alpha export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; @@ -22,7 +19,11 @@ export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; export const scaffolderActionPermissions: ResourcePermission<'scaffolder-action'>[]; // @alpha -export const scaffolderPermissions: ResourcePermission<'scaffolder-template'>[]; +export const scaffolderPermissions: ( + | BasicPermission + | ResourcePermission<'scaffolder-action'> + | ResourcePermission<'scaffolder-template'> +)[]; // @alpha export const scaffolderTaskPermissions: BasicPermission[]; From 03045fc530d8c29cb753ef5b01bc29c2b3585255 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Mon, 6 May 2024 11:51:16 -0700 Subject: [PATCH 024/118] feat: first attempt at adding jwks-auth to external token handlers Signed-off-by: Ryan Hanchett --- docs/auth/service-to-service-auth.md | 32 ++++++++ .../auth/external/ExternalTokenHandler.ts | 3 + .../implementations/auth/external/jwks.ts | 73 +++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 packages/backend-app-api/src/services/implementations/auth/external/jwks.ts diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index c1d6643769..b9cc6151af 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -81,6 +81,38 @@ header: Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW ``` +## JWKS Token Auth + +This access method allows for external caller token authentication using configured JWKS. +This is useful for callers that are authenticating to your instance of Backstage with +third-party tools, such as Auth0. + +You can configure this access method by adding one or more entries of type `jwks` +to the `backend.auth.externalAccess` app-config key: + +```yaml title="in e.g. app-config.production.yaml" +backend: + auth: + externalAccess: + - type: jwks + options: + uri: https://example.com/.well-known/jwks.json + issuers: + - https://example.com + algorithms: + - RS256 + audiences: + - example + - type: jwks + options: + uri: https://another-example.com/.well-known/jwks.json + issuers: + - https://example.com +``` + +The subject returned from the token verification will become part of the +credentials object that the request recipients get. + ## Legacy Tokens Plugins and backends that are _not_ on the new backend system use a legacy token diff --git a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts index 588a1f1794..79ad8dd3c4 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts @@ -21,6 +21,7 @@ import { import { LegacyTokenHandler } from './legacy'; import { StaticTokenHandler } from './static'; import { TokenHandler } from './types'; +import { JWKSHandler } from './jwks'; const NEW_CONFIG_KEY = 'backend.auth.externalAccess'; const OLD_CONFIG_KEY = 'backend.auth.keys'; @@ -40,9 +41,11 @@ export class ExternalTokenHandler { const staticHandler = new StaticTokenHandler(); const legacyHandler = new LegacyTokenHandler(); + const jwksHandler = new JWKSHandler(); const handlers: Record = { static: staticHandler, legacy: legacyHandler, + jwks: jwksHandler, }; // Load the new-style handlers diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts new file mode 100644 index 0000000000..6ca7d010b2 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -0,0 +1,73 @@ +/* + * 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. + */ + +import { jwtVerify, createRemoteJWKSet } from 'jose'; +import { Config } from '@backstage/config'; +import { TokenHandler } from './types'; + +/** + * Handles `type: jwks` access. + * + * @internal + */ +export class JWKSHandler implements TokenHandler { + #entries: Array<{ + algorithms: string[]; + audiences: string[]; + issuers: string[]; + uri: string; + }> = []; + + add(options: Config) { + const algorithms = options.getOptionalStringArray('algorithms') ?? []; + const issuers = options.getOptionalStringArray('issuers') ?? []; + const audiences = options.getOptionalStringArray('audiences') ?? []; + const uri = options.getString('uri'); + + if (!uri.match(/^\S+$/)) { + throw new Error('Illegal token, must be a set of non-space characters'); + } + + if (!issuers.every(issuer => issuer.match(/^\S+$/))) { + throw new Error('Illegal issuer, must be a set of non-space characters'); + } + + this.#entries.push({ algorithms, audiences, issuers, uri }); + } + + async verifyToken(token: string) { + // not sure if we would need to support multiple jwks entries, but implementing to match static/legacy token handlers + for (const entry of this.#entries) { + try { + const jwks = createRemoteJWKSet(new URL(entry.uri)); + const { + payload: { sub }, + } = await jwtVerify(token, jwks, { + algorithms: entry.algorithms, + issuer: entry.issuers, + audience: entry.audiences, + }); + + if (sub) { + return { subject: sub }; + } + } catch { + continue; + } + } + return undefined; + } +} From e978badcebaeab431f53180ce58f4f2ceefb5582 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 7 May 2024 09:55:53 -0700 Subject: [PATCH 025/118] test: add unit tests for jwks access Signed-off-by: Ryan Hanchett --- .../auth/external/jwks.test.ts | 202 ++++++++++++++++++ .../implementations/auth/external/jwks.ts | 12 +- 2 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts new file mode 100644 index 0000000000..c4d9f37b23 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts @@ -0,0 +1,202 @@ +/* + * 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. + */ +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { SignJWT, exportJWK, generateKeyPair } from 'jose'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { v4 as uuid } from 'uuid'; +import { JWKSHandler } from './jwks'; + +interface AnyJWK extends Record { + use: 'sig'; + alg: string; + kid: string; + kty: string; +} +// Simplified copy of TokenFactory in @backstage/plugin-auth-backend +// Since this is re-used in several tests, I wonder if it should get refactored +// into @backstage/backend-test-utils +class FakeTokenFactory { + private readonly keys = new Array(); + + constructor( + private readonly options: { + issuer: string; + keyDurationSeconds: number; + }, + ) {} + + async issueToken(params: { + claims: { + sub: string; + ent?: string[]; + }; + }): Promise { + const pair = await generateKeyPair('RS256'); + const publicKey = await exportJWK(pair.publicKey); + const kid = uuid(); + publicKey.kid = kid; + this.keys.push(publicKey as AnyJWK); + + const iss = this.options.issuer; + const sub = params.claims.sub; + const ent = params.claims.ent; + const aud = 'backstage'; + const iat = Math.floor(Date.now() / 1000); + const exp = iat + this.options.keyDurationSeconds; + + return new SignJWT({ iss, sub, aud, iat, exp, ent, kid }) + .setProtectedHeader({ alg: 'RS256', ent: ent, kid: kid }) + .setIssuer(iss) + .setAudience(aud) + .setSubject(sub) + .setIssuedAt(iat) + .setExpirationTime(exp) + .sign(pair.privateKey); + } + + async listPublicKeys(): Promise<{ keys: AnyJWK[] }> { + return { keys: this.keys }; + } +} + +const server = setupServer(); +const mockBaseUrl = 'http://backstage:9191/i-am-a-mock-base'; + +describe('JWKSHandler', () => { + let factory: FakeTokenFactory; + let mockSubject: string; + const keyDurationSeconds = 5; + + setupRequestMockHandlers(server); + + beforeEach(() => { + mockSubject = 'test_subject'; + + factory = new FakeTokenFactory({ + issuer: mockBaseUrl, + keyDurationSeconds, + }); + + server.use( + rest.get(`${mockBaseUrl}/.well-known/jwks.json`, async (_, res, ctx) => { + const keys = await factory.listPublicKeys(); + return res(ctx.json(keys)); + }), + ); + }); + + it('verifies token with valid entry', async () => { + const validEntry = { + uri: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['RS256'], + issuers: [mockBaseUrl], + audiences: ['backstage'], + }; + const jwksHandler = new JWKSHandler(); + + jwksHandler.add(new ConfigReader(validEntry)); + + const token = await factory.issueToken({ + claims: { sub: mockSubject }, + }); + + const result = await jwksHandler.verifyToken(token); + + expect(result).toEqual({ subject: mockSubject }); + }); + + it('skips invalid entry and continues verification', async () => { + const invalidEntry = { + uri: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['RS256'], + issuers: ['fakeIssuer'], + audiences: ['fakeAud'], + }; + + const validEntry = { + uri: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['RS256'], + issuers: ['multiple-issuers', mockBaseUrl], + audiences: ['multiple-audiences', 'backstage'], + }; + const jwksHandler = new JWKSHandler(); + + jwksHandler.add(new ConfigReader(invalidEntry)); + jwksHandler.add(new ConfigReader(validEntry)); + + const token = await factory.issueToken({ + claims: { sub: mockSubject }, + }); + + const result = await jwksHandler.verifyToken(token); + + expect(result).toEqual({ subject: mockSubject }); + }); + + it('returns undefined if no valid entry found', async () => { + const invalidEntry1 = { + uri: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['RS256'], + issuers: [mockBaseUrl], + audiences: [], + }; + + const invalidEntry2 = { + uri: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['HS256'], + issuers: [], + audiences: ['backstage'], + }; + const jwksHandler = new JWKSHandler(); + + jwksHandler.add(new ConfigReader(invalidEntry1)); + jwksHandler.add(new ConfigReader(invalidEntry2)); + + const token = await factory.issueToken({ + claims: { sub: mockSubject }, + }); + + const result = await jwksHandler.verifyToken(token); + + expect(result).toBeUndefined(); + }); + + it('rejects bad config', () => { + const jwksHandler = new JWKSHandler(); + + expect(() => { + jwksHandler.add( + new ConfigReader({ + uri: 'https://exampl e.com/jwks', + }), + ); + }).toThrow('Illegal URI, must be a set of non-space characters'); + expect(() => { + jwksHandler.add( + new ConfigReader({ + uri: 'https://example.com/jwks\n', + }), + ); + }).toThrow('Illegal URI, must be a set of non-space characters'); + }); + + it('gracefully handles no added tokens', async () => { + const handler = new JWKSHandler(); + await expect(handler.verifyToken('ghi')).resolves.toBeUndefined(); + }); +}); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index 6ca7d010b2..34683647df 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -26,7 +26,7 @@ import { TokenHandler } from './types'; export class JWKSHandler implements TokenHandler { #entries: Array<{ algorithms: string[]; - audiences: string[]; + audiences: string[] | string; issuers: string[]; uri: string; }> = []; @@ -34,22 +34,18 @@ export class JWKSHandler implements TokenHandler { add(options: Config) { const algorithms = options.getOptionalStringArray('algorithms') ?? []; const issuers = options.getOptionalStringArray('issuers') ?? []; - const audiences = options.getOptionalStringArray('audiences') ?? []; + // if audience is unset, an empty string is valid, but an empty array is not + const audiences = options.getOptionalStringArray('audiences') ?? ''; const uri = options.getString('uri'); if (!uri.match(/^\S+$/)) { - throw new Error('Illegal token, must be a set of non-space characters'); - } - - if (!issuers.every(issuer => issuer.match(/^\S+$/))) { - throw new Error('Illegal issuer, must be a set of non-space characters'); + throw new Error('Illegal URI, must be a set of non-space characters'); } this.#entries.push({ algorithms, audiences, issuers, uri }); } async verifyToken(token: string) { - // not sure if we would need to support multiple jwks entries, but implementing to match static/legacy token handlers for (const entry of this.#entries) { try { const jwks = createRemoteJWKSet(new URL(entry.uri)); From 23dff40aa2865a52c37f32faae270fa71e8700f8 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 7 May 2024 10:59:31 -0700 Subject: [PATCH 026/118] docs: expand on jwks docs Signed-off-by: Ryan Hanchett --- docs/auth/service-to-service-auth.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index b9cc6151af..9ecd39ba8f 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -83,9 +83,9 @@ Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW ## JWKS Token Auth -This access method allows for external caller token authentication using configured JWKS. -This is useful for callers that are authenticating to your instance of Backstage with -third-party tools, such as Auth0. +This access method allows for external caller token authentication using configured +JSON Web Key Sets (JWKS). This is useful for callers that are authenticating to our +instance of Backstage with third-party tools, such as Auth0. You can configure this access method by adding one or more entries of type `jwks` to the `backend.auth.externalAccess` app-config key: @@ -110,8 +110,22 @@ backend: - https://example.com ``` +The URI should point at an unauthenticated endpoint that returns the JWKS. + +Issuers specifies the issuer(s) of the JWT that the authenticating app will accept. +Passed JWTs must have an `iss` claim which matches one of the specified issuers. + +Algorithms specifies the algorithm(s) that are used to verify the JWT. The passed JWTs +must have been signed using one of the listed algorithms. + +Audiences speficies the intended audience(s) of the JWT. The passed JWTs must have an "aud" +claim that matches one of the audiences specified, or have no audience specified. + +For additional details regarding the JWKS configuration, please consult your authentication +provider's documentation. + The subject returned from the token verification will become part of the -credentials object that the request recipients get. +credentials object that the request recipient plugins get. ## Legacy Tokens From 398b82a3685bd0c623b5cf75063ba6d09b66ee9c Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 7 May 2024 13:53:46 -0700 Subject: [PATCH 027/118] chore: add changeset Signed-off-by: Ryan Hanchett --- .changeset/famous-monkeys-count.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-monkeys-count.md diff --git a/.changeset/famous-monkeys-count.md b/.changeset/famous-monkeys-count.md new file mode 100644 index 0000000000..c5151b38c3 --- /dev/null +++ b/.changeset/famous-monkeys-count.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Add support for JWKS tokens in ExternalTokenHandler. From 9b0db3f495e04a8381c91bd1e7d5f3168d1067cc Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 7 May 2024 14:08:45 -0700 Subject: [PATCH 028/118] chore: clean up comments before opening PR Signed-off-by: Ryan Hanchett --- .../src/services/implementations/auth/external/jwks.test.ts | 2 -- .../src/services/implementations/auth/external/jwks.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts index c4d9f37b23..95df6f56ff 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts @@ -28,8 +28,6 @@ interface AnyJWK extends Record { kty: string; } // Simplified copy of TokenFactory in @backstage/plugin-auth-backend -// Since this is re-used in several tests, I wonder if it should get refactored -// into @backstage/backend-test-utils class FakeTokenFactory { private readonly keys = new Array(); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index 34683647df..5c3738504d 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -34,7 +34,6 @@ export class JWKSHandler implements TokenHandler { add(options: Config) { const algorithms = options.getOptionalStringArray('algorithms') ?? []; const issuers = options.getOptionalStringArray('issuers') ?? []; - // if audience is unset, an empty string is valid, but an empty array is not const audiences = options.getOptionalStringArray('audiences') ?? ''; const uri = options.getString('uri'); From c88b3ee688617d6d993a3a76c438825dcbad1182 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 15:19:26 +0000 Subject: [PATCH 029/118] chore(deps): update dependency @types/webpack-env to v1.18.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- microsite/yarn.lock | 6 +++--- yarn.lock | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 3ca3e2837a..f25dda1cbd 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -3237,9 +3237,9 @@ __metadata: linkType: hard "@types/webpack-env@npm:^1.18.0": - version: 1.18.4 - resolution: "@types/webpack-env@npm:1.18.4" - checksum: f195b3ae974ac3b631477b57737dad7b6c44ecca86770cf3c29f284e02961c9f2dfc619e3e253d8c23966864cb052b1e8437e9834ede32ac97972e6e2235bb51 + version: 1.18.5 + resolution: "@types/webpack-env@npm:1.18.5" + checksum: 4ca8eb4c44e1e1807c3e245442fce7aaf2816a163056de9436bbac44cc47c8bc5b1c9a330dc05748d6616431b1fb5bd5379733fb1da0b78d03c59f4ec824c184 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index 5c700c6fdc..10928fcdc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18542,9 +18542,9 @@ __metadata: linkType: hard "@types/webpack-env@npm:^1.15.2, @types/webpack-env@npm:^1.15.3": - version: 1.18.4 - resolution: "@types/webpack-env@npm:1.18.4" - checksum: f195b3ae974ac3b631477b57737dad7b6c44ecca86770cf3c29f284e02961c9f2dfc619e3e253d8c23966864cb052b1e8437e9834ede32ac97972e6e2235bb51 + version: 1.18.5 + resolution: "@types/webpack-env@npm:1.18.5" + checksum: 4ca8eb4c44e1e1807c3e245442fce7aaf2816a163056de9436bbac44cc47c8bc5b1c9a330dc05748d6616431b1fb5bd5379733fb1da0b78d03c59f4ec824c184 languageName: node linkType: hard From 96e30c54ab9dd8c966b9495fdcd8e13a7f6d7545 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Wed, 8 May 2024 09:12:28 -0700 Subject: [PATCH 030/118] fix: rename uri to url Signed-off-by: Ryan Hanchett --- docs/auth/service-to-service-auth.md | 6 +++--- .../src/services/implementations/auth/external/jwks.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index 9ecd39ba8f..e678c82477 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -96,7 +96,7 @@ backend: externalAccess: - type: jwks options: - uri: https://example.com/.well-known/jwks.json + url: https://example.com/.well-known/jwks.json issuers: - https://example.com algorithms: @@ -105,12 +105,12 @@ backend: - example - type: jwks options: - uri: https://another-example.com/.well-known/jwks.json + url: https://another-example.com/.well-known/jwks.json issuers: - https://example.com ``` -The URI should point at an unauthenticated endpoint that returns the JWKS. +The URL should point at an unauthenticated endpoint that returns the JWKS. Issuers specifies the issuer(s) of the JWT that the authenticating app will accept. Passed JWTs must have an `iss` claim which matches one of the specified issuers. diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index 5c3738504d..070f33ed53 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -28,26 +28,26 @@ export class JWKSHandler implements TokenHandler { algorithms: string[]; audiences: string[] | string; issuers: string[]; - uri: string; + url: string; }> = []; add(options: Config) { const algorithms = options.getOptionalStringArray('algorithms') ?? []; const issuers = options.getOptionalStringArray('issuers') ?? []; const audiences = options.getOptionalStringArray('audiences') ?? ''; - const uri = options.getString('uri'); + const url = options.getString('url'); - if (!uri.match(/^\S+$/)) { + if (!url.match(/^\S+$/)) { throw new Error('Illegal URI, must be a set of non-space characters'); } - this.#entries.push({ algorithms, audiences, issuers, uri }); + this.#entries.push({ algorithms, audiences, issuers, url }); } async verifyToken(token: string) { for (const entry of this.#entries) { try { - const jwks = createRemoteJWKSet(new URL(entry.uri)); + const jwks = createRemoteJWKSet(new URL(entry.url)); const { payload: { sub }, } = await jwtVerify(token, jwks, { From 8443332f72f5c90bf53724433bcef44ec755bba7 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Wed, 8 May 2024 09:18:06 -0700 Subject: [PATCH 031/118] fix: default to undefined for algo, iss and aud fields if not set in config Signed-off-by: Ryan Hanchett --- .../services/implementations/auth/external/jwks.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index 070f33ed53..dd3df07435 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -25,20 +25,20 @@ import { TokenHandler } from './types'; */ export class JWKSHandler implements TokenHandler { #entries: Array<{ - algorithms: string[]; - audiences: string[] | string; - issuers: string[]; + algorithms: string[] | undefined; + audiences: string[] | undefined; + issuers: string[] | undefined; url: string; }> = []; add(options: Config) { - const algorithms = options.getOptionalStringArray('algorithms') ?? []; - const issuers = options.getOptionalStringArray('issuers') ?? []; - const audiences = options.getOptionalStringArray('audiences') ?? ''; + const algorithms = options.getOptionalStringArray('algorithms'); + const issuers = options.getOptionalStringArray('issuers'); + const audiences = options.getOptionalStringArray('audiences'); const url = options.getString('url'); if (!url.match(/^\S+$/)) { - throw new Error('Illegal URI, must be a set of non-space characters'); + throw new Error('Illegal URL, must be a set of non-space characters'); } this.#entries.push({ algorithms, audiences, issuers, url }); From e5ade53cd97903b7f26e8247ba1fdc52ebdb5656 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Wed, 8 May 2024 09:41:33 -0700 Subject: [PATCH 032/118] feat: add subjectPrefix config Signed-off-by: Ryan Hanchett --- docs/auth/service-to-service-auth.md | 5 +++- .../implementations/auth/external/jwks.ts | 24 ++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index e678c82477..0b2aa369ec 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -103,6 +103,7 @@ backend: - RS256 audiences: - example + subjectPrefix: custom-prefix - type: jwks options: url: https://another-example.com/.well-known/jwks.json @@ -125,7 +126,9 @@ For additional details regarding the JWKS configuration, please consult your aut provider's documentation. The subject returned from the token verification will become part of the -credentials object that the request recipient plugins get. +credentials object that the request recipient plugins get. All subjects will have the prefix +`external:`, but you can also provide a custom subjectPrefix which will get appended before the +subject returned from your JWKS service (ex. `external:custom-prefix:sub`). ## Legacy Tokens diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index dd3df07435..d734cbf984 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -25,29 +25,31 @@ import { TokenHandler } from './types'; */ export class JWKSHandler implements TokenHandler { #entries: Array<{ - algorithms: string[] | undefined; - audiences: string[] | undefined; - issuers: string[] | undefined; - url: string; + algorithms?: string[]; + audiences?: string[]; + issuers?: string[]; + subjectPrefix?: string; + url: URL; }> = []; add(options: Config) { const algorithms = options.getOptionalStringArray('algorithms'); const issuers = options.getOptionalStringArray('issuers'); const audiences = options.getOptionalStringArray('audiences'); - const url = options.getString('url'); + const subjectPrefix = options.getOptionalString('subjectPrefix'); + const url = new URL(options.getString('url')); - if (!url.match(/^\S+$/)) { + if (!options.getString('url').match(/^\S+$/)) { throw new Error('Illegal URL, must be a set of non-space characters'); } - this.#entries.push({ algorithms, audiences, issuers, url }); + this.#entries.push({ algorithms, audiences, issuers, subjectPrefix, url }); } async verifyToken(token: string) { for (const entry of this.#entries) { try { - const jwks = createRemoteJWKSet(new URL(entry.url)); + const jwks = createRemoteJWKSet(entry.url); const { payload: { sub }, } = await jwtVerify(token, jwks, { @@ -57,7 +59,11 @@ export class JWKSHandler implements TokenHandler { }); if (sub) { - return { subject: sub }; + if (entry.subjectPrefix) { + return { subject: `external:${entry.subjectPrefix}:${sub}` }; + } + + return { subject: `external:${sub}` }; } } catch { continue; From e54e0c47c551c1bec224591cb16a90333cd2747b Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Wed, 8 May 2024 09:46:56 -0700 Subject: [PATCH 033/118] test: fix tests, add new test for custom subject prefix Signed-off-by: Ryan Hanchett --- .../auth/external/jwks.test.ts | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts index 95df6f56ff..4cbdcb1cb0 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts @@ -100,7 +100,7 @@ describe('JWKSHandler', () => { it('verifies token with valid entry', async () => { const validEntry = { - uri: `${mockBaseUrl}/.well-known/jwks.json`, + url: `${mockBaseUrl}/.well-known/jwks.json`, algorithms: ['RS256'], issuers: [mockBaseUrl], audiences: ['backstage'], @@ -115,19 +115,19 @@ describe('JWKSHandler', () => { const result = await jwksHandler.verifyToken(token); - expect(result).toEqual({ subject: mockSubject }); + expect(result).toEqual({ subject: `external:${mockSubject}` }); }); it('skips invalid entry and continues verification', async () => { const invalidEntry = { - uri: `${mockBaseUrl}/.well-known/jwks.json`, + url: `${mockBaseUrl}/.well-known/jwks.json`, algorithms: ['RS256'], issuers: ['fakeIssuer'], audiences: ['fakeAud'], }; const validEntry = { - uri: `${mockBaseUrl}/.well-known/jwks.json`, + url: `${mockBaseUrl}/.well-known/jwks.json`, algorithms: ['RS256'], issuers: ['multiple-issuers', mockBaseUrl], audiences: ['multiple-audiences', 'backstage'], @@ -143,19 +143,19 @@ describe('JWKSHandler', () => { const result = await jwksHandler.verifyToken(token); - expect(result).toEqual({ subject: mockSubject }); + expect(result).toEqual({ subject: `external:${mockSubject}` }); }); it('returns undefined if no valid entry found', async () => { const invalidEntry1 = { - uri: `${mockBaseUrl}/.well-known/jwks.json`, + url: `${mockBaseUrl}/.well-known/jwks.json`, algorithms: ['RS256'], issuers: [mockBaseUrl], audiences: [], }; const invalidEntry2 = { - uri: `${mockBaseUrl}/.well-known/jwks.json`, + url: `${mockBaseUrl}/.well-known/jwks.json`, algorithms: ['HS256'], issuers: [], audiences: ['backstage'], @@ -180,21 +180,44 @@ describe('JWKSHandler', () => { expect(() => { jwksHandler.add( new ConfigReader({ - uri: 'https://exampl e.com/jwks', + url: 'https://exampl e.com/jwks', }), ); - }).toThrow('Illegal URI, must be a set of non-space characters'); + }).toThrow('Invalid URL'); expect(() => { jwksHandler.add( new ConfigReader({ - uri: 'https://example.com/jwks\n', + url: 'https://example.com/jwks\n', }), ); - }).toThrow('Illegal URI, must be a set of non-space characters'); + }).toThrow('Illegal URL, must be a set of non-space characters'); }); it('gracefully handles no added tokens', async () => { const handler = new JWKSHandler(); await expect(handler.verifyToken('ghi')).resolves.toBeUndefined(); }); + + it('uses custom subject prefix if provided', async () => { + const validEntry = { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['RS256'], + issuers: [mockBaseUrl], + audiences: ['backstage'], + subjectPrefix: 'custom-prefix', + }; + const jwksHandler = new JWKSHandler(); + + jwksHandler.add(new ConfigReader(validEntry)); + + const token = await factory.issueToken({ + claims: { sub: mockSubject }, + }); + + const result = await jwksHandler.verifyToken(token); + + expect(result).toEqual({ + subject: `external:${validEntry.subjectPrefix}:${mockSubject}`, + }); + }); }); From 9a0c4795b7a9dc5b31e088bec4e3e8b127d6ccb7 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Wed, 8 May 2024 13:39:52 -0400 Subject: [PATCH 034/118] docs(scaffolder-backend): update documentation with new scaffolder permissions Signed-off-by: Frank Kong --- ...der-tasks-parameters-steps-and-actions.md} | 67 +++++++++++++++++-- microsite/sidebars.json | 2 +- 2 files changed, 63 insertions(+), 6 deletions(-) rename docs/features/software-templates/{authorizing-parameters-steps-and-actions.md => authorizing-scaffolder-tasks-parameters-steps-and-actions.md} (77%) diff --git a/docs/features/software-templates/authorizing-parameters-steps-and-actions.md b/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md similarity index 77% rename from docs/features/software-templates/authorizing-parameters-steps-and-actions.md rename to docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md index 072f750a14..76bbf39d30 100644 --- a/docs/features/software-templates/authorizing-parameters-steps-and-actions.md +++ b/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md @@ -1,10 +1,10 @@ --- -id: authorizing-parameters-steps-and-actions -title: 'Authorizing parameters, steps and actions' -description: How to authorize part of a template +id: authorizing-scaffolder-tasks-parameters-steps-and-actions +title: 'Authorizing scaffolder tasks parameters, steps and actions' +description: How to authorize part of a template and authorize scaffolder task access --- -The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template. +The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template. It also allows you to control access to scaffolder tasks. ### Authorizing parameters and steps @@ -174,7 +174,64 @@ class ExamplePermissionPolicy implements PermissionPolicy { } ``` -Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex cases. +### Authorizing scaffolder tasks + +The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to the scaffolder. + +```ts title="packages/src/backend/plugins/permissions.ts" +/* highlight-add-start */ +import { + taskCancelPermission, + taskCreatePermission, + taskReadPermission, +} from '@backstage/plugin-scaffolder-common/alpha'; +/* highlight-add-end */ + +class ExamplePermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + user?: BackstageIdentityResponse, + ): Promise { + /* highlight-add-start */ + if (isPermission(request.permission, taskCreatePermission)) { + if (user?.identity.userEntityRef === 'user:default/spiderman') { + return { + result: AuthorizeResult.ALLOW, + }; + } + } + if (isPermission(request.permission, taskCancelPermission)) { + if (user?.identity.userEntityRef === 'user:default/spiderman') { + return { + result: AuthorizeResult.ALLOW, + }; + } + } + if (isPermission(request.permission, taskReadPermission)) { + if (user?.identity.userEntityRef === 'user:default/spiderman') { + return { + result: AuthorizeResult.ALLOW, + }; + } + } + /* highlight-add-end */ + + return { + result: AuthorizeResult.DENY, + }; + } +} +``` + +In the provided example permission policy, we only grant the `spiderman` user permissions to perform/access the following actions/resources: + +- Read all scaffolder tasks and their associated events/logs. +- Cancel any ongoing scaffolder tasks. +- Trigger software templates, which effectively creates new scaffolder tasks. + +Any other user would be denied access to these actions/resources. + +Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex use cases. ### Authorizing in the New Backend System diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 6d9ba3a680..f2fc4afdaa 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -129,7 +129,7 @@ "features/software-templates/writing-tests-for-actions", "features/software-templates/writing-custom-field-extensions", "features/software-templates/writing-custom-step-layouts", - "features/software-templates/authorizing-parameters-steps-and-actions", + "features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions", "features/software-templates/migrating-to-rjsf-v5", "features/software-templates/migrating-from-v1beta2-to-v1beta3" ] From 9a328699b8d6db802ddc2f03388c823d432dea10 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Wed, 8 May 2024 13:44:59 -0400 Subject: [PATCH 035/118] chore: update changesets Signed-off-by: Frank Kong --- .changeset/tender-seas-listen.md | 1 - .changeset/weak-gifts-occur.md | 1 - 2 files changed, 2 deletions(-) diff --git a/.changeset/tender-seas-listen.md b/.changeset/tender-seas-listen.md index cfcea07574..86b0bf4618 100644 --- a/.changeset/tender-seas-listen.md +++ b/.changeset/tender-seas-listen.md @@ -9,4 +9,3 @@ updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend comp - `scaffolder.task.create` - `scaffolder.task.cancel` - `scaffolder.task.read` -- `scaffolder.action.read` diff --git a/.changeset/weak-gifts-occur.md b/.changeset/weak-gifts-occur.md index c1a65e7dff..7834c0a9d9 100644 --- a/.changeset/weak-gifts-occur.md +++ b/.changeset/weak-gifts-occur.md @@ -8,4 +8,3 @@ added the following new permissions to the scaffolder backend endpoints: - `scaffolder.task.create` - `scaffolder.task.cancel` - `scaffolder.task.read` -- `scaffolder.action.read` From b8d12d8c5ec65338ae873b790d70436ab3605f17 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Fri, 10 May 2024 13:00:50 +0530 Subject: [PATCH 036/118] Updated the accessibility document Signed-off-by: Aditya Kumar --- docs/accessibility/index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/accessibility/index.md b/docs/accessibility/index.md index b4d63fd4e4..da10d60dd2 100644 --- a/docs/accessibility/index.md +++ b/docs/accessibility/index.md @@ -47,7 +47,10 @@ If you want to use the Lighthouse CLI and run the checks based on the config you yarn dlx @lhci/cli@0.11.x autorun ``` -> Note: running this command will use the [Lighthouse config](https://github.com/backstage/backstage/blob/39ba2284d73885b7ca8290cb38e2b1e4d983c8d6/lighthouserc.js#L19-L34) so make sure to adjust it to your needs if needed. +:::note Note +Running this command will use the [Lighthouse config](https://github.com/backstage/backstage/blob/39ba2284d73885b7ca8290cb38e2b1e4d983c8d6/lighthouserc.js#L19-L34) so make sure to adjust it to your needs if needed. + +::: ### Use Lighthouse Github Action on your own repo From 97df97d3317921d85faecb2c3724eb2513ac3d46 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 11:06:59 +0000 Subject: [PATCH 037/118] chore(deps): update ossf/scorecard-action action to v2.3.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 0b883dd75a..a51e3fefe8 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -39,7 +39,7 @@ jobs: persist-credentials: false - name: 'Run analysis' - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 with: results_file: results.sarif results_format: sarif From 4668dc76c97150de63d108f7c17b05b2a10cffe6 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Mon, 13 May 2024 18:50:50 +0530 Subject: [PATCH 038/118] variable name change Signed-off-by: npiyush97 --- .../src/modules/core/AnnotateLocationEntityProcessor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts index 83638d205f..f46f5cfd42 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts @@ -53,14 +53,14 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor { let viewUrl; let editUrl; let sourceLocation; - const gitCommitBranchURLPattern = /\b[0-9a-f]{40,}\b/; + const commitHashRegExp = /\b[0-9a-f]{40,}\b/; if (location.type === 'url') { const scmIntegration = integrations.byUrl(location.target); viewUrl = location.target; - if (!gitCommitBranchURLPattern.test(location.target)) { + if (!commitHashRegExp.test(location.target)) { editUrl = scmIntegration?.resolveEditUrl(location.target); } From 81a215d3e694b7814839a0fdef5b9500ce5e9833 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Mon, 13 May 2024 22:44:00 +0530 Subject: [PATCH 039/118] added changes Signed-off-by: npiyush97 --- .changeset/thirty-plums-shout.md | 2 +- .../src/modules/core/AnnotateLocationEntityProcessor.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/thirty-plums-shout.md b/.changeset/thirty-plums-shout.md index 5b5ffad6a9..56ac640326 100644 --- a/.changeset/thirty-plums-shout.md +++ b/.changeset/thirty-plums-shout.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': patch --- -Added a regex test to check commit hash.If url is from git commit branch ignore the edit url. +Added a regex test to check commit hash. If url is from git commit branch ignore the edit url. diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts index f46f5cfd42..00864b69e7 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts @@ -31,6 +31,7 @@ import { CatalogProcessorEmit, } from '@backstage/plugin-catalog-node'; +const commitHashRegExp = /\b[0-9a-f]{40,}\b/; /** @public */ export class AnnotateLocationEntityProcessor implements CatalogProcessor { constructor( @@ -53,7 +54,6 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor { let viewUrl; let editUrl; let sourceLocation; - const commitHashRegExp = /\b[0-9a-f]{40,}\b/; if (location.type === 'url') { const scmIntegration = integrations.byUrl(location.target); From 5c11b14f9daa4386b5a1ce7ef01246f7e7ce430b Mon Sep 17 00:00:00 2001 From: JeevaRamanathan Date: Wed, 15 May 2024 20:42:37 +0530 Subject: [PATCH 040/118] updated note in documentation Signed-off-by: JeevaRamanathan --- docs/integrations/github/org.md | 10 +++++++--- docs/integrations/gitlab/discovery.md | 6 +++++- docs/integrations/gitlab/org.md | 6 +++++- docs/integrations/ldap/org.md | 8 ++++++-- docs/permissions/getting-started.md | 6 +++++- .../04-authorizing-access-to-paginated-data.md | 6 +++++- .../plugin-authors/05-frontend-authorization.md | 6 +++++- docs/plugins/backend-plugin.md | 6 +++++- 8 files changed, 43 insertions(+), 11 deletions(-) diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 06b4e07daa..f94ffe6c7a 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -17,9 +17,13 @@ is a hierarchy of [`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind entities that mirror your org setup. -> Note: This adds `User` and `Group` entities to the catalog, but does not -> provide authentication. See the -> [GitHub auth provider](../../auth/github/provider.md) for that. +:::note Note + +This adds `User` and `Group` entities to the catalog, but does not +provide authentication. See the +[GitHub auth provider](../../auth/github/provider.md) for that. + +::: ## Permissions diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 91efe05fee..b16f6db839 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -136,7 +136,11 @@ To use the discovery provider, you'll need a GitLab integration [set up](locations.md) with a `token`. Then you can add a provider config per group to the catalog configuration. -> > NOTE: if you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. +:::note Note + +If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. + +::: ```yaml title="app-config.yaml" catalog: diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md index 14587461e5..2cc0dcfb82 100644 --- a/docs/integrations/gitlab/org.md +++ b/docs/integrations/gitlab/org.md @@ -158,7 +158,11 @@ amount of data, this can take significant time and resources. The token used must have the `read_api` scope, and the Users and Groups fetched will be those visible to the account which provisioned the token. -> > NOTE: if you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. +:::note Note + +If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. + +::: ```yaml catalog: diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index 49a2776fb1..a10bc3918c 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -29,8 +29,12 @@ to `@backstage/plugin-catalog-backend-module-ldap` to your backend package. yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap ``` -> Note: When configuring to use a Provider instead of a Processor you do not -> need to add a _location_ pointing to your LDAP server +:::note Note + +When configuring to use a Provider instead of a Processor you do not +need to add a _location_ pointing to your LDAP server + +::: Update the catalog plugin initialization in your backend to add the provider and schedule it: diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index e241f0e38e..4dabf3fcbd 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -8,7 +8,11 @@ If you prefer to watch a video instead, you can start with this video introducti -> Note: This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. +:::note Note + +This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. + +::: Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index a9e62040c5..84e86140fc 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -36,7 +36,11 @@ This approach will work for simple cases, but it has a downside: it forces us to To avoid this situation, the permissions framework has support for filtering items in the data source itself. In this part of the tutorial, we'll describe the steps required to use that behavior. -> Note: in order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. +:::note Note + +In order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. + +::: ## Creating the read permission diff --git a/docs/permissions/plugin-authors/05-frontend-authorization.md b/docs/permissions/plugin-authors/05-frontend-authorization.md index 92d855698f..60458aaf4d 100644 --- a/docs/permissions/plugin-authors/05-frontend-authorization.md +++ b/docs/permissions/plugin-authors/05-frontend-authorization.md @@ -8,7 +8,11 @@ In the previous sections, we learned how to protect our plugin's backend API rou Take, for example, the "Add" button in our todo list application. When a user clicks this button, the frontend makes a `POST` request to the `/todos` route of our backend. If a user tries to add a todo but is not authorized, they will have no way of knowing this until they perform the action and are faced with an error. This is a poor user experience. We can do better by disabling the add button. -> Note: Placing frontend components behind authorization cannot take the place of placing your backend routes behind authorization. Authorization checks on the frontend should be used in _addition_ to the corresponding backend authorization, as an improvement to the user experience. If you do not place your backend route behind authorization, a malicious actor can still send a request to the route even if you disabled the corresponding frontend component. +:::note Note + +Placing frontend components behind authorization cannot take the place of placing your backend routes behind authorization. Authorization checks on the frontend should be used in _addition_ to the corresponding backend authorization, as an improvement to the user experience. If you do not place your backend route behind authorization, a malicious actor can still send a request to the route even if you disabled the corresponding frontend component. + +::: ## Using `usePermission` diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 7a51cb635c..255ab4f865 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -44,7 +44,11 @@ cd plugins/carmen-backend yarn start ``` -> Note: this documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md) +:::note Note + +This documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md) + +::: This will think for a bit, and then say `Listening on :7007`. In a different terminal window, now run From 8721a02da15268e02cbfe7e11d71ab7fa228a5e4 Mon Sep 17 00:00:00 2001 From: Symbat Nurbay Date: Thu, 16 May 2024 15:28:33 +0200 Subject: [PATCH 041/118] repo-tools: add additional properties to generate command Signed-off-by: Symbat Nurbay --- .changeset/strong-moose-work.md | 5 +++++ packages/repo-tools/src/commands/index.ts | 4 ++++ .../package/schema/openapi/generate/client.ts | 16 +++++++++++++--- .../package/schema/openapi/generate/index.ts | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 .changeset/strong-moose-work.md diff --git a/.changeset/strong-moose-work.md b/.changeset/strong-moose-work.md new file mode 100644 index 0000000000..0c9cf01d94 --- /dev/null +++ b/.changeset/strong-moose-work.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': minor +--- + +Add --additional-properties option to generate command to pass properties to @openapitools/openapi-generator-cli diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 83266ea9f9..a07b01077f 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -54,6 +54,10 @@ function registerPackageCommand(program: Command) { .description( 'Command to generate a client and/or a server stub from an OpenAPI spec.', ) + .option('--additional-properties [properties]') + .description( + 'Additional properties that can be passed to @openapitools/openapi-generator-cli', + ) .action( lazy(() => import('./package/schema/openapi/generate').then(m => m.command), diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts index 6963e02664..1f4b2e5152 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts @@ -27,12 +27,18 @@ import { exec } from '../../../../../lib/exec'; import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { getPathToCurrentOpenApiSpec } from '../../../../../lib/openapi/helpers'; -async function generate(outputDirectory: string) { +async function generate( + outputDirectory: string, + additionalProperties?: string, +) { const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); const resolvedOutputDirectory = cliPaths.resolveTargetRoot( outputDirectory, OUTPUT_PATH, ); + const openapiProperties = additionalProperties + ? `--additional-properties=${additionalProperties}` + : ''; mkdirpSync(resolvedOutputDirectory); await fs.mkdirp(resolvedOutputDirectory); @@ -60,6 +66,7 @@ async function generate(outputDirectory: string) { ), '--generator-key', 'v3.0', + openapiProperties, ], { maxBuffer: Number.MAX_VALUE, @@ -87,9 +94,12 @@ async function generate(outputDirectory: string) { }); } -export async function command(outputPackage: string): Promise { +export async function command( + outputPackage: string, + additionalProperties?: string, +): Promise { try { - await generate(outputPackage); + await generate(outputPackage, additionalProperties); console.log( chalk.green(`Generated client in ${outputPackage}/${OUTPUT_PATH}`), ); diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts index 1e48fe3825..5356db3244 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts @@ -26,7 +26,7 @@ export async function command(opts: OptionValues) { process.exit(1); } if (opts.clientPackage) { - await generateClient(opts.clientPackage); + await generateClient(opts.clientPackage, opts.additionalProperties); } if (opts.server) { await generateServer(); From 0714031ca8b1bd8342dc192b73b5443e514bcf06 Mon Sep 17 00:00:00 2001 From: Symbat Nurbay Date: Fri, 17 May 2024 10:15:23 +0200 Subject: [PATCH 042/118] repo-tools: rename --additional-properties to --client-additional-properties Signed-off-by: Symbat Nurbay --- packages/repo-tools/src/commands/index.ts | 2 +- .../package/schema/openapi/generate/client.ts | 12 ++++++------ .../package/schema/openapi/generate/index.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index a07b01077f..24ec2af411 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -54,7 +54,7 @@ function registerPackageCommand(program: Command) { .description( 'Command to generate a client and/or a server stub from an OpenAPI spec.', ) - .option('--additional-properties [properties]') + .option('--client-additional-properties [properties]') .description( 'Additional properties that can be passed to @openapitools/openapi-generator-cli', ) diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts index 1f4b2e5152..b843cd27ec 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts @@ -29,15 +29,15 @@ import { getPathToCurrentOpenApiSpec } from '../../../../../lib/openapi/helpers' async function generate( outputDirectory: string, - additionalProperties?: string, + clientAdditionalProperties?: string, ) { const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); const resolvedOutputDirectory = cliPaths.resolveTargetRoot( outputDirectory, OUTPUT_PATH, ); - const openapiProperties = additionalProperties - ? `--additional-properties=${additionalProperties}` + const additionalProperties = clientAdditionalProperties + ? `--additional-properties=${clientAdditionalProperties}` : ''; mkdirpSync(resolvedOutputDirectory); @@ -66,7 +66,7 @@ async function generate( ), '--generator-key', 'v3.0', - openapiProperties, + additionalProperties, ], { maxBuffer: Number.MAX_VALUE, @@ -96,10 +96,10 @@ async function generate( export async function command( outputPackage: string, - additionalProperties?: string, + clientAdditionalProperties?: string, ): Promise { try { - await generate(outputPackage, additionalProperties); + await generate(outputPackage, clientAdditionalProperties); console.log( chalk.green(`Generated client in ${outputPackage}/${OUTPUT_PATH}`), ); diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts index 5356db3244..41884e2e90 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts @@ -26,7 +26,7 @@ export async function command(opts: OptionValues) { process.exit(1); } if (opts.clientPackage) { - await generateClient(opts.clientPackage, opts.additionalProperties); + await generateClient(opts.clientPackage, opts.clientAdditionalProperties); } if (opts.server) { await generateServer(); From 70b51b218b97900d2fa74a1e512cc651269d6fc7 Mon Sep 17 00:00:00 2001 From: Symbat Nurbay Date: Fri, 17 May 2024 10:20:14 +0200 Subject: [PATCH 043/118] repo-tools: change changeset Signed-off-by: Symbat Nurbay --- .changeset/strong-moose-work.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/strong-moose-work.md b/.changeset/strong-moose-work.md index 0c9cf01d94..20d35da7f0 100644 --- a/.changeset/strong-moose-work.md +++ b/.changeset/strong-moose-work.md @@ -2,4 +2,4 @@ '@backstage/repo-tools': minor --- -Add --additional-properties option to generate command to pass properties to @openapitools/openapi-generator-cli +Add --client-additional-properties option to generate command to pass properties to @openapitools/openapi-generator-cli From 0177f7589286bf0aa1918c17f13d844ded32c1c0 Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Fri, 17 May 2024 16:17:37 -0400 Subject: [PATCH 044/118] fix: move kubernetes autoscaling to v2 Signed-off-by: Matthew Clarke --- .changeset/stupid-tigers-bake.md | 8 ++ .../dice-roller/dice-roller-manifests.yaml | 2 +- .../src/service/KubernetesFanOutHandler.ts | 2 +- plugins/kubernetes-common/api-report.md | 6 +- .../__fixtures__/hpa-healthy.json | 75 ++++++++--- .../__fixtures__/hpa-maxed-out.json | 75 ++++++++--- .../error-detection/error-detection.test.ts | 6 +- .../src/error-detection/error-detection.ts | 2 +- .../src/error-detection/hpas.ts | 4 +- plugins/kubernetes-common/src/types.ts | 6 +- plugins/kubernetes-react/api-report.md | 4 +- .../src/__fixtures__/1-deployments.json | 123 +++++++++--------- .../src/__fixtures__/1-statefulsets.json | 123 +++++++++--------- .../src/__fixtures__/2-deployments.json | 119 ++++++++--------- .../src/__fixtures__/2-statefulsets.json | 123 +++++++++--------- .../CustomResources/ArgoRollouts/Rollout.tsx | 19 ++- .../DeploymentsAccordions.tsx | 20 ++- .../HorizontalPodAutoscalerDrawer.tsx | 21 ++- .../horizontalpodautoscalers.json | 115 ++++++++-------- .../StatefulSetsAccordions.tsx | 20 ++- plugins/kubernetes-react/src/utils/owner.ts | 6 +- .../src/__fixtures__/1-deployments.json | 123 +++++++++--------- .../src/__fixtures__/1-statefulsets.json | 123 +++++++++--------- .../src/__fixtures__/2-deployments.json | 123 +++++++++--------- .../src/__fixtures__/2-statefulsets.json | 123 +++++++++--------- 25 files changed, 725 insertions(+), 646 deletions(-) create mode 100644 .changeset/stupid-tigers-bake.md diff --git a/.changeset/stupid-tigers-bake.md b/.changeset/stupid-tigers-bake.md new file mode 100644 index 0000000000..8572caa675 --- /dev/null +++ b/.changeset/stupid-tigers-bake.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-kubernetes-backend': minor +'@backstage/plugin-kubernetes-common': minor +'@backstage/plugin-kubernetes-react': minor +'@backstage/plugin-kubernetes': minor +--- + +Update kubernetes plugins to use autoscaling/v2 diff --git a/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml b/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml index 8f225e666a..4d564b7552 100644 --- a/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml +++ b/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml @@ -221,7 +221,7 @@ spec: - containerPort: 80 --- -apiVersion: autoscaling/v1 +apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: dice-roller diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 53b5070e61..54a038fffd 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -103,7 +103,7 @@ export const DEFAULT_OBJECTS: ObjectToFetch[] = [ }, { group: 'autoscaling', - apiVersion: 'v1', + apiVersion: 'v2', plural: 'horizontalpodautoscalers', objectType: 'horizontalpodautoscalers', }, diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index 2f46ef90e5..81e9ca28e7 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -14,7 +14,6 @@ import { V1ConfigMap } from '@kubernetes/client-node'; import { V1CronJob } from '@kubernetes/client-node'; import { V1DaemonSet } from '@kubernetes/client-node'; import { V1Deployment } from '@kubernetes/client-node'; -import { V1HorizontalPodAutoscaler } from '@kubernetes/client-node'; import { V1Ingress } from '@kubernetes/client-node'; import { V1Job } from '@kubernetes/client-node'; import { V1LimitRange } from '@kubernetes/client-node'; @@ -23,6 +22,7 @@ import { V1ReplicaSet } from '@kubernetes/client-node'; import { V1ResourceQuota } from '@kubernetes/client-node'; import { V1Service } from '@kubernetes/client-node'; import { V1StatefulSet } from '@kubernetes/client-node'; +import { V2HorizontalPodAutoscaler } from '@kubernetes/client-node'; // @public export const ANNOTATION_KUBERNETES_API_SERVER = 'kubernetes.io/api-server'; @@ -192,7 +192,7 @@ export interface DeploymentResources { // (undocumented) deployments: V1Deployment[]; // (undocumented) - horizontalPodAutoscalers: V1HorizontalPodAutoscaler[]; + horizontalPodAutoscalers: V2HorizontalPodAutoscaler[]; // (undocumented) pods: V1Pod[]; // (undocumented) @@ -294,7 +294,7 @@ export const groupResponses: ( // @public (undocumented) export interface HorizontalPodAutoscalersFetchResponse { // (undocumented) - resources: Array; + resources: Array; // (undocumented) type: 'horizontalpodautoscalers'; } diff --git a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json index 23edee5a07..29c74b4839 100644 --- a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json +++ b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json @@ -1,32 +1,77 @@ { + "apiVersion": "autoscaling/v2", + "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API: the server could not find the requested resource (get pods.metrics.k8s.io)\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2020-09-28T13:28:00.000Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, "name": "dice-roller", "namespace": "default", - "resourceVersion": "698957", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "a70c8a90-5605-4d7d-adea-05cfb8d9d446" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 13, - "desiredReplicas": 14, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } diff --git a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json index 4466e7b4b1..29c74b4839 100644 --- a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json +++ b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json @@ -1,32 +1,77 @@ { + "apiVersion": "autoscaling/v2", + "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API: the server could not find the requested resource (get pods.metrics.k8s.io)\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2020-09-28T13:28:00.000Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, "name": "dice-roller", "namespace": "default", - "resourceVersion": "698957", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "a70c8a90-5605-4d7d-adea-05cfb8d9d446" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 10, - "minReplicas": 5, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 70 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 10, - "currentCPUUtilizationPercentage": 100 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } diff --git a/plugins/kubernetes-common/src/error-detection/error-detection.test.ts b/plugins/kubernetes-common/src/error-detection/error-detection.test.ts index ae4605c3c5..f57226a01c 100644 --- a/plugins/kubernetes-common/src/error-detection/error-detection.test.ts +++ b/plugins/kubernetes-common/src/error-detection/error-detection.test.ts @@ -17,7 +17,7 @@ import { V1Pod, V1Deployment, - V1HorizontalPodAutoscaler, + V2HorizontalPodAutoscaler, } from '@kubernetes/client-node'; import { detectErrors } from './error-detection'; import * as healthyPod from './__fixtures__/pod.json'; @@ -61,7 +61,7 @@ const oneDeployment = (deployment: V1Deployment): ObjectsByEntityResponse => { }); }; -const oneHpa = (hpa: V1HorizontalPodAutoscaler): ObjectsByEntityResponse => { +const oneHpa = (hpa: V2HorizontalPodAutoscaler): ObjectsByEntityResponse => { return oneItem({ type: 'horizontalpodautoscalers', resources: [hpa], @@ -328,7 +328,7 @@ describe('detectErrors', () => { expect(err1).toStrictEqual({ sourceRef: { - apiGroup: 'autoscaling/v1', + apiGroup: 'autoscaling/v2', kind: 'HorizontalPodAutoscaler', name: 'dice-roller', namespace: 'default', diff --git a/plugins/kubernetes-common/src/error-detection/error-detection.ts b/plugins/kubernetes-common/src/error-detection/error-detection.ts index a3a4d58d9e..6fa9211db4 100644 --- a/plugins/kubernetes-common/src/error-detection/error-detection.ts +++ b/plugins/kubernetes-common/src/error-detection/error-detection.ts @@ -21,7 +21,7 @@ import { detectErrorsInPods } from './pods'; import { detectErrorsInDeployments } from './deployments'; import { detectErrorsInHpa } from './hpas'; import { Deployment } from 'kubernetes-models/apps/v1'; -import { HorizontalPodAutoscaler } from 'kubernetes-models/autoscaling/v1'; +import { HorizontalPodAutoscaler } from 'kubernetes-models/autoscaling/v2'; import { Pod } from 'kubernetes-models/v1'; /** diff --git a/plugins/kubernetes-common/src/error-detection/hpas.ts b/plugins/kubernetes-common/src/error-detection/hpas.ts index 6f15c119b6..785e6b0e9b 100644 --- a/plugins/kubernetes-common/src/error-detection/hpas.ts +++ b/plugins/kubernetes-common/src/error-detection/hpas.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { HorizontalPodAutoscaler } from 'kubernetes-models/autoscaling/v1'; +import { HorizontalPodAutoscaler } from 'kubernetes-models/autoscaling/v2'; import { DetectedError, ErrorMapper } from './types'; import { detectErrorsInObjects } from './common'; @@ -35,7 +35,7 @@ const hpaErrorMappers: ErrorMapper[] = [ name: hpa.metadata?.name ?? 'unknown hpa', namespace: hpa.metadata?.namespace ?? 'unknown namespace', kind: 'HorizontalPodAutoscaler', - apiGroup: 'autoscaling/v1', + apiGroup: 'autoscaling/v2', }, occurrenceCount: 1, }, diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index c4bb5fd32f..a63c157948 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -21,7 +21,7 @@ import { V1CronJob, V1DaemonSet, V1Deployment, - V1HorizontalPodAutoscaler, + V2HorizontalPodAutoscaler, V1Ingress, V1Job, V1LimitRange, @@ -186,7 +186,7 @@ export interface ResourceQuotaFetchResponse { /** @public */ export interface HorizontalPodAutoscalersFetchResponse { type: 'horizontalpodautoscalers'; - resources: Array; + resources: Array; } /** @public */ @@ -282,7 +282,7 @@ export interface DeploymentResources { pods: V1Pod[]; replicaSets: V1ReplicaSet[]; deployments: V1Deployment[]; - horizontalPodAutoscalers: V1HorizontalPodAutoscaler[]; + horizontalPodAutoscalers: V2HorizontalPodAutoscaler[]; } /** @public */ diff --git a/plugins/kubernetes-react/api-report.md b/plugins/kubernetes-react/api-report.md index ccc2e1643d..2d080d110d 100644 --- a/plugins/kubernetes-react/api-report.md +++ b/plugins/kubernetes-react/api-report.md @@ -33,10 +33,10 @@ import { ProfileInfoApi } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import * as React_3 from 'react'; import { TypeMeta } from '@kubernetes-models/base'; -import { V1HorizontalPodAutoscaler } from '@kubernetes/client-node'; import { V1Job } from '@kubernetes/client-node'; import { V1ObjectMeta } from '@kubernetes/client-node'; import { V1Pod } from '@kubernetes/client-node'; +import { V2HorizontalPodAutoscaler } from '@kubernetes/client-node'; import { WorkloadsByEntityRequest } from '@backstage/plugin-kubernetes-common'; // @public (undocumented) @@ -285,7 +285,7 @@ export const GroupedResponsesContext: React_2.Context; // @public (undocumented) export const HorizontalPodAutoscalerDrawer: (props: { - hpa: V1HorizontalPodAutoscaler; + hpa: V2HorizontalPodAutoscaler; expanded?: boolean; children?: React_2.ReactNode; }) => React_2.JSX.Element; diff --git a/plugins/kubernetes-react/src/__fixtures__/1-deployments.json b/plugins/kubernetes-react/src/__fixtures__/1-deployments.json index 5ad847dc49..3a9da2a33a 100644 --- a/plugins/kubernetes-react/src/__fixtures__/1-deployments.json +++ b/plugins/kubernetes-react/src/__fixtures__/1-deployments.json @@ -2826,85 +2826,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes-react/src/__fixtures__/1-statefulsets.json b/plugins/kubernetes-react/src/__fixtures__/1-statefulsets.json index 6c04774ff1..5d9b56300a 100644 --- a/plugins/kubernetes-react/src/__fixtures__/1-statefulsets.json +++ b/plugins/kubernetes-react/src/__fixtures__/1-statefulsets.json @@ -2827,85 +2827,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes-react/src/__fixtures__/2-deployments.json b/plugins/kubernetes-react/src/__fixtures__/2-deployments.json index f5efdbf1cb..829636b6f3 100644 --- a/plugins/kubernetes-react/src/__fixtures__/2-deployments.json +++ b/plugins/kubernetes-react/src/__fixtures__/2-deployments.json @@ -4434,85 +4434,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { "maxReplicas": 15, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], "minReplicas": 10, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 30, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes-react/src/__fixtures__/2-statefulsets.json b/plugins/kubernetes-react/src/__fixtures__/2-statefulsets.json index 0a8c26c198..c45b1070d6 100644 --- a/plugins/kubernetes-react/src/__fixtures__/2-statefulsets.json +++ b/plugins/kubernetes-react/src/__fixtures__/2-statefulsets.json @@ -4436,85 +4436,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx index 65749c2ea2..8e62b2606f 100644 --- a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx +++ b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx @@ -21,7 +21,7 @@ import AccordionSummary from '@material-ui/core/AccordionSummary'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { V1Pod, V1HorizontalPodAutoscaler } from '@kubernetes/client-node'; +import { V1Pod, V2HorizontalPodAutoscaler } from '@kubernetes/client-node'; import { PodsTable } from '../../Pods'; import { HorizontalPodAutoscalerDrawer } from '../../HorizontalPodAutoscalers'; import { RolloutDrawer } from './RolloutDrawer'; @@ -50,7 +50,7 @@ type RolloutAccordionProps = { rollout: any; ownedPods: V1Pod[]; defaultExpanded?: boolean; - matchingHpa?: V1HorizontalPodAutoscaler; + matchingHpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -58,7 +58,7 @@ type RolloutSummaryProps = { rollout: any; numberOfCurrentPods: number; numberOfPodsWithErrors: number; - hpa?: V1HorizontalPodAutoscaler; + hpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -93,6 +93,13 @@ const RolloutSummary = ({ (p: any) => p.reason === 'CanaryPauseStep', )?.startTime; const abortedMessage = findAbortedMessage(rollout); + const specCpuUtil = hpa?.spec?.metrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.target.averageUtilization; + + const cpuUtil = hpa?.status?.currentMetrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.current.averageUtilization; return ( - current CPU usage:{' '} - {hpa.status?.currentCPUUtilizationPercentage ?? '?'}% + current CPU usage: {cpuUtil ?? '?'}% - target CPU usage:{' '} - {hpa.spec?.targetCPUUtilizationPercentage ?? '?'}% + target CPU usage: {specCpuUtil ?? '?'}% diff --git a/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx b/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx index 3d048f22df..8cfc79a8ae 100644 --- a/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx +++ b/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx @@ -24,7 +24,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { V1Deployment, V1Pod, - V1HorizontalPodAutoscaler, + V2HorizontalPodAutoscaler, } from '@kubernetes/client-node'; import { PodsTable } from '../Pods'; import { DeploymentDrawer } from './DeploymentDrawer'; @@ -47,7 +47,7 @@ type DeploymentsAccordionsProps = { type DeploymentAccordionProps = { deployment: V1Deployment; ownedPods: V1Pod[]; - matchingHpa?: V1HorizontalPodAutoscaler; + matchingHpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -55,7 +55,7 @@ type DeploymentSummaryProps = { deployment: V1Deployment; numberOfCurrentPods: number; numberOfPodsWithErrors: number; - hpa?: V1HorizontalPodAutoscaler; + hpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -65,6 +65,14 @@ const DeploymentSummary = ({ numberOfPodsWithErrors, hpa, }: DeploymentSummaryProps) => { + const specCpuUtil = hpa?.spec?.metrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.target.averageUtilization; + + const cpuUtil = hpa?.status?.currentMetrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.current.averageUtilization; + return ( - current CPU usage:{' '} - {hpa.status?.currentCPUUtilizationPercentage ?? '?'}% + current CPU usage: {cpuUtil ?? '?'}% - target CPU usage:{' '} - {hpa.spec?.targetCPUUtilizationPercentage ?? '?'}% + target CPU usage: {specCpuUtil ?? '?'}% diff --git a/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx b/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx index dba942155c..0194bfca2a 100644 --- a/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx +++ b/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx @@ -15,32 +15,39 @@ */ import React from 'react'; -import { V1HorizontalPodAutoscaler } from '@kubernetes/client-node'; +import { V2HorizontalPodAutoscaler } from '@kubernetes/client-node'; import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; /** @public */ export const HorizontalPodAutoscalerDrawer = (props: { - hpa: V1HorizontalPodAutoscaler; + hpa: V2HorizontalPodAutoscaler; expanded?: boolean; children?: React.ReactNode; }) => { const { hpa, expanded, children } = props; + const specCpuUtil = hpa?.spec?.metrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.target.averageUtilization; + + const cpuUtil = hpa?.status?.currentMetrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.current.averageUtilization; + return ( { + renderObject={(hpaObject: V2HorizontalPodAutoscaler) => { return { - targetCPUUtilizationPercentage: - hpaObject.spec?.targetCPUUtilizationPercentage, - currentCPUUtilizationPercentage: - hpaObject.status?.currentCPUUtilizationPercentage, + targetCPUUtilizationPercentage: specCpuUtil, + currentCPUUtilizationPercentage: cpuUtil, minReplicas: hpaObject.spec?.minReplicas, maxReplicas: hpaObject.spec?.maxReplicas, currentReplicas: hpaObject.status?.currentReplicas, desiredReplicas: hpaObject.status?.desiredReplicas, + lastScaleTime: hpa?.status?.lastScaleTime, }; }} > diff --git a/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/__fixtures__/horizontalpodautoscalers.json b/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/__fixtures__/horizontalpodautoscalers.json index 6afdda48ed..94cc8a840d 100644 --- a/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/__fixtures__/horizontalpodautoscalers.json +++ b/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/__fixtures__/horizontalpodautoscalers.json @@ -1,82 +1,79 @@ [ { + "apiVersion": "autoscaling/v2", + "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API: the server could not find the requested resource (get pods.metrics.k8s.io)\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2020-09-28T13:28:00.000Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2020-09-28T13:28:15.000Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl", - "operation": "Update", - "time": "2020-09-28T13:28:21.000Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "698957", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "a70c8a90-5605-4d7d-adea-05cfb8d9d446" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { "maxReplicas": 15, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 30, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], "minReplicas": 10, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 50, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], "currentReplicas": 13, "desiredReplicas": 14, - "currentCPUUtilizationPercentage": 30 + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx index 00f08c3c98..80fff510e7 100644 --- a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx +++ b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx @@ -23,7 +23,7 @@ import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { V1Pod, - V1HorizontalPodAutoscaler, + V2HorizontalPodAutoscaler, V1StatefulSet, } from '@kubernetes/client-node'; import { PodsTable } from '../Pods'; @@ -44,7 +44,7 @@ type StatefulSetsAccordionsProps = { type StatefulSetAccordionProps = { statefulset: V1StatefulSet; ownedPods: V1Pod[]; - matchingHpa?: V1HorizontalPodAutoscaler; + matchingHpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -52,7 +52,7 @@ type StatefulSetSummaryProps = { statefulset: V1StatefulSet; numberOfCurrentPods: number; numberOfPodsWithErrors: number; - hpa?: V1HorizontalPodAutoscaler; + hpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -62,6 +62,14 @@ const StatefulSetSummary = ({ numberOfPodsWithErrors, hpa, }: StatefulSetSummaryProps) => { + const specCpuUtil = hpa?.spec?.metrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.target.averageUtilization; + + const cpuUtil = hpa?.status?.currentMetrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.current.averageUtilization; + return ( - current CPU usage:{' '} - {hpa.status?.currentCPUUtilizationPercentage ?? '?'}% + current CPU usage: {cpuUtil ?? '?'}% - target CPU usage:{' '} - {hpa.spec?.targetCPUUtilizationPercentage ?? '?'}% + target CPU usage: {specCpuUtil ?? '?'}% diff --git a/plugins/kubernetes-react/src/utils/owner.ts b/plugins/kubernetes-react/src/utils/owner.ts index 1c1d8089a3..bd0cd39677 100644 --- a/plugins/kubernetes-react/src/utils/owner.ts +++ b/plugins/kubernetes-react/src/utils/owner.ts @@ -16,7 +16,7 @@ import { V1ObjectMeta } from '@kubernetes/client-node/dist/gen/model/v1ObjectMeta'; import { - V1HorizontalPodAutoscaler, + V2HorizontalPodAutoscaler, V1Pod, V1ReplicaSet, } from '@kubernetes/client-node'; @@ -62,8 +62,8 @@ interface ResourceRef { export const getMatchingHpa = ( owner: ResourceRef, - hpas: V1HorizontalPodAutoscaler[], -): V1HorizontalPodAutoscaler | undefined => { + hpas: V2HorizontalPodAutoscaler[], +): V2HorizontalPodAutoscaler | undefined => { return hpas.find(hpa => { return ( (hpa.spec?.scaleTargetRef?.kind ?? '').toLocaleLowerCase('en-US') === diff --git a/plugins/kubernetes/src/__fixtures__/1-deployments.json b/plugins/kubernetes/src/__fixtures__/1-deployments.json index 5ad847dc49..3a9da2a33a 100644 --- a/plugins/kubernetes/src/__fixtures__/1-deployments.json +++ b/plugins/kubernetes/src/__fixtures__/1-deployments.json @@ -2826,85 +2826,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes/src/__fixtures__/1-statefulsets.json b/plugins/kubernetes/src/__fixtures__/1-statefulsets.json index 6c04774ff1..5d9b56300a 100644 --- a/plugins/kubernetes/src/__fixtures__/1-statefulsets.json +++ b/plugins/kubernetes/src/__fixtures__/1-statefulsets.json @@ -2827,85 +2827,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes/src/__fixtures__/2-deployments.json b/plugins/kubernetes/src/__fixtures__/2-deployments.json index f5efdbf1cb..c8209e9f0a 100644 --- a/plugins/kubernetes/src/__fixtures__/2-deployments.json +++ b/plugins/kubernetes/src/__fixtures__/2-deployments.json @@ -4434,85 +4434,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes/src/__fixtures__/2-statefulsets.json b/plugins/kubernetes/src/__fixtures__/2-statefulsets.json index 0a8c26c198..c45b1070d6 100644 --- a/plugins/kubernetes/src/__fixtures__/2-statefulsets.json +++ b/plugins/kubernetes/src/__fixtures__/2-statefulsets.json @@ -4436,85 +4436,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] From b8507a1487208fa9e1c6e749e745f681229c5de6 Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Fri, 17 May 2024 16:20:02 -0400 Subject: [PATCH 045/118] fix: changeset Signed-off-by: Matthew Clarke --- .changeset/stupid-tigers-bake.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/stupid-tigers-bake.md b/.changeset/stupid-tigers-bake.md index 8572caa675..a4579ab05a 100644 --- a/.changeset/stupid-tigers-bake.md +++ b/.changeset/stupid-tigers-bake.md @@ -2,7 +2,6 @@ '@backstage/plugin-kubernetes-backend': minor '@backstage/plugin-kubernetes-common': minor '@backstage/plugin-kubernetes-react': minor -'@backstage/plugin-kubernetes': minor --- Update kubernetes plugins to use autoscaling/v2 From 0e8c00cfa6f96e84c551290a37a8cff7f11b9d76 Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Fri, 17 May 2024 16:28:03 -0400 Subject: [PATCH 046/118] fix: missed tests Signed-off-by: Matthew Clarke --- .../src/error-detection/__fixtures__/hpa-healthy.json | 2 +- .../src/error-detection/__fixtures__/hpa-maxed-out.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json index 29c74b4839..2fc73a5ee3 100644 --- a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json +++ b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json @@ -13,7 +13,7 @@ "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 2, + "maxReplicas": 10, "metrics": [ { "resource": { diff --git a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json index 29c74b4839..2360e14ca0 100644 --- a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json +++ b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json @@ -13,7 +13,7 @@ "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 2, + "maxReplicas": 10, "metrics": [ { "resource": { @@ -70,8 +70,8 @@ "type": "Resource" } ], - "currentReplicas": 2, - "desiredReplicas": 2, + "currentReplicas": 10, + "desiredReplicas": 10, "lastScaleTime": "2024-02-13T20:14:23Z" } } From a322d76037c9f1b7d60422e4615f227d5a35db60 Mon Sep 17 00:00:00 2001 From: Eric Roberson Date: Sat, 18 May 2024 09:45:16 -0700 Subject: [PATCH 047/118] chore(catalog): aboutcard tests typo fix typo fix Signed-off-by: Eric Roberson --- plugins/catalog/src/components/AboutCard/AboutCard.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index f90942dcd8..45058c88ed 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -546,7 +546,7 @@ describe('', () => { ).not.toBeInTheDocument(); }); - it('renders techdocs lin when 3rdparty', async () => { + it('renders techdocs link when 3rdparty', async () => { const entity = { apiVersion: 'v1', kind: 'Component', From 03afcf1db50d156536c251d0804a4d1a6fe9d689 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 20 May 2024 22:24:11 +0200 Subject: [PATCH 048/118] fix: remove custom fallback Signed-off-by: ElaineDeMattosSilvaB --- .../src/__testUtils__/handlers.ts | 2 +- .../src/__testUtils__/mocks.ts | 115 +++++++++++++++++- .../GitlabDiscoveryEntityProvider.test.ts | 27 ++-- .../GitlabDiscoveryEntityProvider.ts | 8 +- 4 files changed, 123 insertions(+), 29 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts index 00d4994e94..f2dcac13cd 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts @@ -165,7 +165,7 @@ const httpProjectCatalogDynamic = all_projects_response.map(project => { `${apiBaseUrl}/projects/${path}/repository/files/catalog-info.yaml`, (req, res, ctx) => { const branch = req.url.searchParams.get('ref'); - if (branch === project.default_branch) { + if (branch === (project.default_branch || 'main' || 'develop')) { return res(ctx.status(200)); } return res(ctx.status(404, 'Not Found')); diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts index 5a078ab689..279da0dfc0 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts @@ -190,6 +190,33 @@ export const config_single_integration_branch: MockObject = { }, }; +export const config_single_integration_specific_branch: MockObject = { + integrations: { + gitlab: [ + { + host: 'example.com', + apiBaseUrl: 'https://example.com/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'example.com', + group: 'group1', + branch: 'develop', + skipForkedRepos: false, + schedule: { + frequency: 'PT30M', + timeout: 'PT3M', + }, + }, + }, + }, + }, +}; export const config_single_integration_group: MockObject = { integrations: { gitlab: [ @@ -234,7 +261,7 @@ export const config_fallbackBranch_branch: MockObject = { 'test-id': { host: 'example.com', group: 'group1', - fallbackBranch: 'staging', + fallbackBranch: 'main', skipForkedRepos: false, schedule: { frequency: 'PT30M', @@ -634,7 +661,7 @@ export const all_projects_response: GitLabProject[] = [ web_url: 'https://example.com/group1/test-repo5-staging', path_with_namespace: 'group1/test-repo5-staging', }, - // diffrent group + // different group { id: 6, description: 'Project Six Description', @@ -646,6 +673,17 @@ export const all_projects_response: GitLabProject[] = [ web_url: 'https://example.com/group1/test-repo6', path_with_namespace: 'awesome-group/test-repo6', }, + // no default branch + { + id: 7, + description: 'Project Seven Description', + name: 'test-repo7', + path: 'test-repo7', + archived: false, + last_activity_at: new Date().toString(), + web_url: 'https://example.com/group1/test-repo7', + path_with_namespace: 'group1/test-repo7', + }, ]; export const all_users_response: GitLabUser[] = [ @@ -1299,9 +1337,43 @@ export const push_modif_event: EventParams = { /** * Expected Backstage entities */ -export const expected_location_entities: MockObject[] = + +// includes only projects that have a default branch (for when the branch and default branch were not set in the config) +export const expected_location_entities_default_branch: MockObject[] = + all_projects_response + .filter(project => project.default_branch) + .map(project => { + const targetUrl = `https://example.com/${project.path_with_namespace}/-/blob/${project.default_branch}/catalog-info.yaml`; + + return { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:${targetUrl}`, + 'backstage.io/managed-by-origin-location': `url:${targetUrl}`, + }, + name: locationSpecToMetadataName({ + target: targetUrl, + type: 'url', + }), + }, + spec: { + presence: 'optional', + target: targetUrl, + type: 'url', + }, + }, + locationKey: 'GitlabDiscoveryEntityProvider:test-id', + }; + }); + +// includes every GitLab project that has a default branch and the fallback declared in the config +export const expected_location_entities_fallback_branch: MockObject[] = all_projects_response.map(project => { - const targetUrl = `https://example.com/${project.path_with_namespace}/-/blob/${project.default_branch}/catalog-info.yaml`; + const branch = project.default_branch || 'main'; + const targetUrl = `https://example.com/${project.path_with_namespace}/-/blob/${branch}/catalog-info.yaml`; return { entity: { @@ -1312,7 +1384,40 @@ export const expected_location_entities: MockObject[] = 'backstage.io/managed-by-location': `url:${targetUrl}`, 'backstage.io/managed-by-origin-location': `url:${targetUrl}`, }, - name: locationSpecToMetadataName({ target: targetUrl, type: 'url' }), + name: locationSpecToMetadataName({ + target: targetUrl, + type: 'url', + }), + }, + spec: { + presence: 'optional', + target: targetUrl, + type: 'url', + }, + }, + locationKey: 'GitlabDiscoveryEntityProvider:test-id', + }; + }); + +// includes ONLY the projects with the branch declared in the config +export const expected_location_entities_specific_branch: MockObject[] = + all_projects_response.map(project => { + const branch = 'develop'; + const targetUrl = `https://example.com/${project.path_with_namespace}/-/blob/${branch}/catalog-info.yaml`; + + return { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:${targetUrl}`, + 'backstage.io/managed-by-origin-location': `url:${targetUrl}`, + }, + name: locationSpecToMetadataName({ + target: targetUrl, + type: 'url', + }), }, spec: { presence: 'optional', diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index 7167fc1c2d..0c20732fa7 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -153,7 +153,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter( + entities: mock.expected_location_entities_default_branch.filter( entity => !entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' @@ -187,7 +187,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter( + entities: mock.expected_location_entities_default_branch.filter( entity => entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' @@ -217,7 +217,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter( + entities: mock.expected_location_entities_default_branch.filter( entity => !entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' @@ -229,8 +229,10 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { }); }); - it('should filter found projects based on the branch', async () => { - const config = new ConfigReader(mock.config_single_integration_branch); + it('should only ingest projects from specific branch', async () => { + const config = new ConfigReader( + mock.config_single_integration_specific_branch, + ); const schedule = new PersistingTaskRunner(); const entityProviderConnection: EntityProviderConnection = { applyMutation: jest.fn(), @@ -251,7 +253,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter( + entities: mock.expected_location_entities_specific_branch.filter( entity => entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' @@ -263,7 +265,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { }); }); - it('should only include projects with fallback branch', async () => { + it('should include projects from fallback branch', async () => { const config = new ConfigReader(mock.config_fallbackBranch_branch); const schedule = new PersistingTaskRunner(); const entityProviderConnection: EntityProviderConnection = { @@ -275,21 +277,14 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { schedule, })[0]; - const configured_branch = - mock.config_fallbackBranch_branch.catalog.providers.gitlab['test-id'] - .fallbackBranch; - await provider.connect(entityProviderConnection); await provider.refresh(logger); expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter( + entities: mock.expected_location_entities_fallback_branch.filter( entity => - entity.entity.metadata.annotations[ - 'backstage.io/managed-by-location' - ].includes(configured_branch) && !entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' ].includes('awesome'), @@ -319,7 +314,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter(entity => + entities: mock.expected_location_entities_default_branch.filter(entity => entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' ].includes(configured_group), diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts index 73ff5f3a01..b2ef475d9d 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GitLabIntegration, ScmIntegrations } from '@backstage/integration'; @@ -34,7 +35,6 @@ import { paginated, readGitlabConfigs, } from '../lib'; -import { LoggerService } from '@backstage/backend-plugin-api'; import * as path from 'path'; @@ -470,14 +470,8 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { return false; } - const customFallbackBranch = - this.config.fallbackBranch !== 'master' - ? this.config.fallbackBranch - : undefined; - const project_branch = this.config.branch ?? - customFallbackBranch ?? project.default_branch ?? this.config.fallbackBranch; From 1afb8d43f421fc4e28b39afffe1ccd6b93221bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 May 2024 09:23:24 +0200 Subject: [PATCH 049/118] Update docs/auth/service-to-service-auth.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/auth/service-to-service-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index 0b2aa369ec..246bab4415 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -119,7 +119,7 @@ Passed JWTs must have an `iss` claim which matches one of the specified issuers. Algorithms specifies the algorithm(s) that are used to verify the JWT. The passed JWTs must have been signed using one of the listed algorithms. -Audiences speficies the intended audience(s) of the JWT. The passed JWTs must have an "aud" +Audiences specify the intended audience(s) of the JWT. The passed JWTs must have an "aud" claim that matches one of the audiences specified, or have no audience specified. For additional details regarding the JWKS configuration, please consult your authentication From 22785e3085acaab37c16c3320fb13e336a714892 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Tue, 21 May 2024 09:56:17 +0200 Subject: [PATCH 050/118] Deleting the changeset file as it's not being published Signed-off-by: cmoulliard --- .changeset/few-dodos-cheer.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/few-dodos-cheer.md diff --git a/.changeset/few-dodos-cheer.md b/.changeset/few-dodos-cheer.md deleted file mode 100644 index a80147e112..0000000000 --- a/.changeset/few-dodos-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Register the `catalogPlugin` to the DevApp fixing the issue to launch locally the plugin From d0048635f75e3b857d774be6018231b0db28d927 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Tue, 21 May 2024 11:53:20 +0200 Subject: [PATCH 051/118] chore: improve tests Signed-off-by: ElaineDeMattosSilvaB --- .../src/__testUtils__/handlers.ts | 6 ++- .../src/__testUtils__/mocks.ts | 7 ++- .../GitlabDiscoveryEntityProvider.test.ts | 51 ++++++++++++++----- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts index f2dcac13cd..80aa0370d3 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts @@ -165,7 +165,11 @@ const httpProjectCatalogDynamic = all_projects_response.map(project => { `${apiBaseUrl}/projects/${path}/repository/files/catalog-info.yaml`, (req, res, ctx) => { const branch = req.url.searchParams.get('ref'); - if (branch === (project.default_branch || 'main' || 'develop')) { + if ( + branch === project.default_branch || + branch === 'main' || + branch === 'develop' + ) { return res(ctx.status(200)); } return res(ctx.status(404, 'Not Found')); diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts index 279da0dfc0..e3154eb1ce 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts @@ -162,7 +162,7 @@ export const config_github_host: MockObject = { }, }; -export const config_single_integration_branch: MockObject = { +export const config_single_integration: MockObject = { integrations: { gitlab: [ { @@ -178,7 +178,6 @@ export const config_single_integration_branch: MockObject = { 'test-id': { host: 'example.com', group: 'group1', - branch: 'main', skipForkedRepos: false, schedule: { frequency: 'PT30M', @@ -724,7 +723,7 @@ export const all_users_response: GitLabUser[] = [ avatar_url: 'https://secure.gravatar.com/', web_url: 'https://gitlab.example/luigi_mario', }, - // malfomed email address + // malformed email address { id: 5, username: 'MarioMario', @@ -1338,7 +1337,7 @@ export const push_modif_event: EventParams = { * Expected Backstage entities */ -// includes only projects that have a default branch (for when the branch and default branch were not set in the config) +// includes only projects that have a default branch (for when the branch and fallback branch were not set in the config) export const expected_location_entities_default_branch: MockObject[] = all_projects_response .filter(project => project.default_branch) diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index 0c20732fa7..8c4618efdb 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -61,7 +61,7 @@ describe('GitlabDiscoveryEntityProvider - configuration', () => { }); it('should fail without schedule nor scheduler', () => { - const config = new ConfigReader(mock.config_single_integration_branch); + const config = new ConfigReader(mock.config_single_integration); expect(() => GitlabDiscoveryEntityProvider.fromConfig(config, { @@ -99,7 +99,7 @@ describe('GitlabDiscoveryEntityProvider - configuration', () => { it('should instantiate provider with single simple discovery config', () => { const schedule = new PersistingTaskRunner(); - const config = new ConfigReader(mock.config_single_integration_branch); + const config = new ConfigReader(mock.config_single_integration); const providers = GitlabDiscoveryEntityProvider.fromConfig(config, { logger, schedule, @@ -229,7 +229,36 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { }); }); - it('should only ingest projects from specific branch', async () => { + // branch and fallback branch are undefined in the config + it('should ingest catalog from project default branch only', async () => { + const config = new ConfigReader(mock.config_single_integration); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + const provider = GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + await provider.connect(entityProviderConnection); + + await provider.refresh(logger); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: mock.expected_location_entities_default_branch.filter( + entity => + !entity.entity.metadata.annotations[ + 'backstage.io/managed-by-location' + ].includes('awesome'), + ), + }); + }); + + // branch was set in the config + it('should ingest catalog from specific branch only', async () => { const config = new ConfigReader( mock.config_single_integration_specific_branch, ); @@ -243,10 +272,6 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { schedule, })[0]; - const configured_branch = - mock.config_single_integration_branch.catalog.providers.gitlab['test-id'] - .branch; - await provider.connect(entityProviderConnection); await provider.refresh(logger); @@ -255,9 +280,6 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { type: 'full', entities: mock.expected_location_entities_specific_branch.filter( entity => - entity.entity.metadata.annotations[ - 'backstage.io/managed-by-location' - ].includes(configured_branch) && !entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' ].includes('awesome'), @@ -265,7 +287,8 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { }); }); - it('should include projects from fallback branch', async () => { + // fallback branch was set in the config + it('should ingest catalog from default or fallback branch', async () => { const config = new ConfigReader(mock.config_fallbackBranch_branch); const schedule = new PersistingTaskRunner(); const entityProviderConnection: EntityProviderConnection = { @@ -389,7 +412,7 @@ describe('GitlabDiscoveryEntityProvider - events', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); }); it('should apply delta mutations on added files from push event', async () => { - const config = new ConfigReader(mock.config_single_integration_branch); + const config = new ConfigReader(mock.config_single_integration); const schedule = new PersistingTaskRunner(); const events = DefaultEventsService.create({ logger }); @@ -416,7 +439,7 @@ describe('GitlabDiscoveryEntityProvider - events', () => { }); it('should apply delta mutations on removed files from push event', async () => { - const config = new ConfigReader(mock.config_single_integration_branch); + const config = new ConfigReader(mock.config_single_integration); const schedule = new PersistingTaskRunner(); const events = DefaultEventsService.create({ logger }); const entityProviderConnection: EntityProviderConnection = { @@ -442,7 +465,7 @@ describe('GitlabDiscoveryEntityProvider - events', () => { }); it('should call refresh on added files from push event', async () => { - const config = new ConfigReader(mock.config_single_integration_branch); + const config = new ConfigReader(mock.config_single_integration); const schedule = new PersistingTaskRunner(); const events = DefaultEventsService.create({ logger }); const entityProviderConnection: EntityProviderConnection = { From f27116436b4216f04e51ce2ca21859b4630ca8b9 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Tue, 21 May 2024 12:02:04 +0200 Subject: [PATCH 052/118] chore: add changeset Signed-off-by: ElaineDeMattosSilvaB --- .changeset/perfect-bikes-invite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/perfect-bikes-invite.md diff --git a/.changeset/perfect-bikes-invite.md b/.changeset/perfect-bikes-invite.md new file mode 100644 index 0000000000..82734b451d --- /dev/null +++ b/.changeset/perfect-bikes-invite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Fixed bug in the GitLab discovery where the fallback branch was taking precedence over the GitLab default branch. Relates to issue #24825. From 153bbd9c60c3b8b26d678edb6db3d99733cb9913 Mon Sep 17 00:00:00 2001 From: Elaine Mattos Date: Tue, 21 May 2024 13:28:33 +0200 Subject: [PATCH 053/118] Update .changeset/perfect-bikes-invite.md Co-authored-by: Vincenzo Scamporlino Signed-off-by: Elaine Mattos --- .changeset/perfect-bikes-invite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/perfect-bikes-invite.md b/.changeset/perfect-bikes-invite.md index 82734b451d..96a4ad5659 100644 --- a/.changeset/perfect-bikes-invite.md +++ b/.changeset/perfect-bikes-invite.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend-module-gitlab': patch --- -Fixed bug in the GitLab discovery where the fallback branch was taking precedence over the GitLab default branch. Relates to issue #24825. +Fixed an issue in `GitlabDiscoveryEntityProvider` where the fallback branch was taking precedence over the GitLab default branch. From a2d26490e618a8a77f176c4853463ecc5a29aed4 Mon Sep 17 00:00:00 2001 From: Bruno Bastos Guimaraes Date: Tue, 21 May 2024 08:38:18 -0300 Subject: [PATCH 054/118] plugins: export catalogTranslationRef Signed-off-by: Bruno Bastos Guimaraes --- .changeset/tall-lies-fetch.md | 5 +++++ plugins/catalog/api-report-alpha.md | 10 ++++++++++ plugins/catalog/src/alpha.ts | 1 + 3 files changed, 16 insertions(+) create mode 100644 .changeset/tall-lies-fetch.md diff --git a/.changeset/tall-lies-fetch.md b/.changeset/tall-lies-fetch.md new file mode 100644 index 0000000000..391aa95aa5 --- /dev/null +++ b/.changeset/tall-lies-fetch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Variable 'catalogTranslationRef' is exported in translation.ts, but it was forgotten to also add it to the alpha entrypoint, so the code never became "visible" diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 3226d10270..75d7e53873 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -11,6 +11,16 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { PortableSchema } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; + +// @alpha (undocumented) +export const catalogTranslationRef: TranslationRef< + 'catalog', + { + readonly 'indexPage.title': '{{orgName}} Catalog'; + readonly 'indexPage.createButtonTitle': 'Create'; + } +>; // @alpha (undocumented) export function createCatalogFilterExtension< diff --git a/plugins/catalog/src/alpha.ts b/plugins/catalog/src/alpha.ts index e80f131817..927d5362b4 100644 --- a/plugins/catalog/src/alpha.ts +++ b/plugins/catalog/src/alpha.ts @@ -16,3 +16,4 @@ export * from './alpha/index'; export { default } from './alpha/index'; +export { catalogTranslationRef } from './translation'; From a112bb59ea5f54093edd9d99fdd8b2e88398da81 Mon Sep 17 00:00:00 2001 From: Frank Kong <50030060+Zaperex@users.noreply.github.com> Date: Tue, 21 May 2024 08:50:05 -0400 Subject: [PATCH 055/118] Update plugins/scaffolder-backend/src/util/checkPermissions.ts Co-authored-by: Vincenzo Scamporlino Signed-off-by: Frank Kong <50030060+Zaperex@users.noreply.github.com> --- plugins/scaffolder-backend/src/util/checkPermissions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/util/checkPermissions.ts b/plugins/scaffolder-backend/src/util/checkPermissions.ts index 40aa673b3f..b6c0395fbb 100644 --- a/plugins/scaffolder-backend/src/util/checkPermissions.ts +++ b/plugins/scaffolder-backend/src/util/checkPermissions.ts @@ -36,8 +36,8 @@ export type checkPermissionOptions = { export async function checkPermission(options: checkPermissionOptions) { const { permissions, permissionService, credentials } = options; if (permissionService) { - const permissionRequest = permissions.map(resourcePermission => ({ - permission: resourcePermission, + const permissionRequest = permissions.map(permission => ({ + permission, })); const authorizationResponses = await permissionService.authorize( permissionRequest, From 3d71ade01ef2bafbe42a01739e71779c065f53f6 Mon Sep 17 00:00:00 2001 From: Frank Kong <50030060+Zaperex@users.noreply.github.com> Date: Tue, 21 May 2024 10:14:53 -0400 Subject: [PATCH 056/118] Update docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: Frank Kong <50030060+Zaperex@users.noreply.github.com> --- ...authorizing-scaffolder-tasks-parameters-steps-and-actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md b/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md index 9ca4a230cc..27696af1a9 100644 --- a/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md +++ b/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md @@ -176,7 +176,7 @@ class ExamplePermissionPolicy implements PermissionPolicy { ### Authorizing scaffolder tasks -The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to the scaffolder. +The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to these areas of the scaffolder. ```ts title="packages/src/backend/plugins/permissions.ts" /* highlight-add-start */ From a1218fc1718f48197c1ac044556b7b7de36c4cb4 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Tue, 21 May 2024 10:56:23 -0400 Subject: [PATCH 057/118] chore: address review comments for docs Signed-off-by: Frank Kong --- ...ctions.md => authorizing-scaffolder-template-details.md} | 6 +++--- microsite/docusaurus.config.ts | 4 ++++ microsite/sidebars.json | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) rename docs/features/software-templates/{authorizing-scaffolder-tasks-parameters-steps-and-actions.md => authorizing-scaffolder-template-details.md} (97%) diff --git a/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md b/docs/features/software-templates/authorizing-scaffolder-template-details.md similarity index 97% rename from docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md rename to docs/features/software-templates/authorizing-scaffolder-template-details.md index 27696af1a9..127677757c 100644 --- a/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md +++ b/docs/features/software-templates/authorizing-scaffolder-template-details.md @@ -1,7 +1,7 @@ --- -id: authorizing-scaffolder-tasks-parameters-steps-and-actions -title: 'Authorizing scaffolder tasks parameters, steps and actions' -description: How to authorize part of a template and authorize scaffolder task access +id: authorizing-scaffolder-template-details +title: 'Authorizing scaffolder tasks, parameters, steps, and actions' +description: How to authorize parts of a template and authorize scaffolder task access --- The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template. It also allows you to control access to scaffolder tasks. diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index e697c5024f..75a67406b6 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -171,6 +171,10 @@ const config: Config = { from: '/docs/getting-started/configuration', to: '/docs/getting-started/#next-steps', }, + { + from: '/docs/features/software-templates/authorizing-parameters-steps-and-actions', + to: '/docs/features/software-templates/authorizing-scaffolder-template-details', + }, ], }, ], diff --git a/microsite/sidebars.json b/microsite/sidebars.json index bfb9215705..a3d9f4dbec 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -130,7 +130,7 @@ "features/software-templates/writing-tests-for-actions", "features/software-templates/writing-custom-field-extensions", "features/software-templates/writing-custom-step-layouts", - "features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions", + "features/software-templates/authorizing-scaffolder-template-details", "features/software-templates/migrating-to-rjsf-v5", "features/software-templates/migrating-from-v1beta2-to-v1beta3" ] From 276da6543d16955e92a8edd5f46c5097b05fb386 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 21 May 2024 09:54:45 -0700 Subject: [PATCH 058/118] fix: create JWKS as part of adding handler instead of in verification step Signed-off-by: Ryan Hanchett --- .../implementations/auth/external/jwks.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index d734cbf984..ea62b3880c 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { jwtVerify, createRemoteJWKSet } from 'jose'; +import { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose'; import { Config } from '@backstage/config'; import { TokenHandler } from './types'; @@ -30,6 +30,7 @@ export class JWKSHandler implements TokenHandler { issuers?: string[]; subjectPrefix?: string; url: URL; + jwks: JWTVerifyGetKey; }> = []; add(options: Config) { @@ -38,21 +39,28 @@ export class JWKSHandler implements TokenHandler { const audiences = options.getOptionalStringArray('audiences'); const subjectPrefix = options.getOptionalString('subjectPrefix'); const url = new URL(options.getString('url')); + const jwks = createRemoteJWKSet(url); if (!options.getString('url').match(/^\S+$/)) { throw new Error('Illegal URL, must be a set of non-space characters'); } - this.#entries.push({ algorithms, audiences, issuers, subjectPrefix, url }); + this.#entries.push({ + algorithms, + audiences, + issuers, + jwks, + subjectPrefix, + url, + }); } async verifyToken(token: string) { for (const entry of this.#entries) { try { - const jwks = createRemoteJWKSet(entry.url); const { payload: { sub }, - } = await jwtVerify(token, jwks, { + } = await jwtVerify(token, entry.jwks, { algorithms: entry.algorithms, issuer: entry.issuers, audience: entry.audiences, From 922bdddcfa935220271695bdab241503e58f8207 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 21 May 2024 10:05:25 -0700 Subject: [PATCH 059/118] fix: add missing config values to config.d.ts Signed-off-by: Ryan Hanchett --- packages/backend-app-api/config.d.ts | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index f84493828a..5517af4f98 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -131,6 +131,47 @@ export interface Config { subject: string; }; } + | { + /** + * This access method consists of a JWKS endpoint that can be used to + * verify JWT tokens. + * + * Callers generate JWT tokens via 3rd party tooling + * and pass them in the Authorization header: + * + * ``` + * Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW + * ``` + */ + type: 'jwks'; + options: { + /** + * Sets the algorithms that should be used to verify the JWT tokens. + * The passed JWTs must have been signed using one of the listed algorithms. + */ + algorithms?: string[]; + /** + * Sets the issuers that should be used to verify the JWT tokens. + * Passed JWTs must have an `iss` claim which matches one of the specified issuers. + */ + issuers?: string[]; + /** + * Sets the audiences that should be used to verify the JWT tokens. + * The passed JWTs must have an "aud" claim that matches one of the audiences specified, + * or have no audience specified. + */ + audiences?: string[]; + /** + * Sets an optional subject prefix. Passes the subject to called plugins. + * Useful for debugging and tracking purposes. + */ + subjectPrefix?: string; + /** + * Sets the URL containing the JWKS endpoint. + */ + url: string; + }; + } >; }; }; From 4ad64d160a4ce77e3e5f01a15a73b0a1d41e2e09 Mon Sep 17 00:00:00 2001 From: Marc Palm <17670840+marcpalm@users.noreply.github.com> Date: Wed, 22 May 2024 10:34:36 +0200 Subject: [PATCH 060/118] fix: remove async imports https://github.com/backstage/backstage/issues/24864 Signed-off-by: Marc Palm <17670840+marcpalm@users.noreply.github.com> Signed-off-by: Marc Palm --- packages/cli/src/lib/bundler/server.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 53f3b2c25a..29298dcc5f 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -165,12 +165,12 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be }); if (process.env.EXPERIMENTAL_VITE) { - const vite = await import('vite'); - const { default: viteReact } = await import('@vitejs/plugin-react'); - const { nodePolyfills: viteNodePolyfills } = await import( + const vite = require('vite'); + const { default: viteReact } = require('@vitejs/plugin-react'); + const { nodePolyfills: viteNodePolyfills } = require( 'vite-plugin-node-polyfills' ); - const { createHtmlPlugin: viteHtml } = await import('vite-plugin-html'); + const { createHtmlPlugin: viteHtml } = require('vite-plugin-html'); viteServer = await vite.createServer({ define: { global: 'window', From 111f7cf05d906dae1835b87c6057981e152d37f2 Mon Sep 17 00:00:00 2001 From: Marc Palm Date: Wed, 22 May 2024 10:45:24 +0200 Subject: [PATCH 061/118] fix: prettier Signed-off-by: Marc Palm --- packages/cli/src/lib/bundler/server.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 29298dcc5f..1da15cd37e 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -167,9 +167,9 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be if (process.env.EXPERIMENTAL_VITE) { const vite = require('vite'); const { default: viteReact } = require('@vitejs/plugin-react'); - const { nodePolyfills: viteNodePolyfills } = require( - 'vite-plugin-node-polyfills' - ); + const { + nodePolyfills: viteNodePolyfills, + } = require('vite-plugin-node-polyfills'); const { createHtmlPlugin: viteHtml } = require('vite-plugin-html'); viteServer = await vite.createServer({ define: { From e187e99b4702d7acf02dad9e84cca217b1b7982c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 22 May 2024 15:21:24 +0200 Subject: [PATCH 062/118] port proxy tests to msw2 to try to get rid of spurious build errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/proxy-backend/package.json | 2 +- .../src/service/router.config.test.ts | 15 ++++++--------- .../src/service/router.credentials.test.ts | 15 +++++++-------- yarn.lock | 2 +- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index d92dba9069..1ca6cbe5f5 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -74,7 +74,7 @@ "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", "@types/yup": "^0.32.0", - "msw": "^1.0.0", + "msw": "^2.0.0", "node-fetch": "^2.6.7", "portfinder": "^1.0.32", "supertest": "^6.1.3" diff --git a/plugins/proxy-backend/src/service/router.config.test.ts b/plugins/proxy-backend/src/service/router.config.test.ts index 0de4787658..20aed5452a 100644 --- a/plugins/proxy-backend/src/service/router.config.test.ts +++ b/plugins/proxy-backend/src/service/router.config.test.ts @@ -24,7 +24,7 @@ import { StaticConfigSource, } from '@backstage/config-loader'; import express from 'express'; -import { rest } from 'msw'; +import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import request from 'supertest'; import { createRouter } from './router'; @@ -35,14 +35,11 @@ import { mockServices } from '@backstage/backend-test-utils'; describe('createRouter reloadable configuration', () => { const server = setupServer( - rest.get('https://non-existing-example.com/', (req, res, ctx) => - res( - ctx.status(200), - ctx.json({ - url: req.url.toString(), - headers: req.headers.all(), - }), - ), + http.get('https://non-existing-example.com/', req => + HttpResponse.json({ + url: req.request.url.toString(), + headers: req.request.headers, + }), ), ); diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts index 1496b6ffb6..85fad9f038 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/backend-test-utils'; import { ResponseError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; -import { rest } from 'msw'; +import { http, HttpResponse, passthrough } from 'msw'; import { setupServer } from 'msw/node'; import fetch from 'node-fetch'; import portFinder from 'portfinder'; @@ -82,13 +82,12 @@ describe('credentials', () => { }; worker.use( - rest.all(`${baseUrl}/*`, req => req.passthrough()), - rest.get('http://target.com/*', (req, res, ctx) => { - const auth = req.headers.get('authorization'); - return res( - ctx.status(200), - ctx.json({ payload: { forwardedAuthorization: auth ?? false } }), - ); + http.all(`${baseUrl}/*`, () => passthrough()), + http.get('http://target.com/*', req => { + const auth = req.request.headers.get('authorization'); + return HttpResponse.json({ + payload: { forwardedAuthorization: auth ?? false }, + }); }), ); diff --git a/yarn.lock b/yarn.lock index d006daa23c..a823457dce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6471,7 +6471,7 @@ __metadata: express-promise-router: ^4.1.0 http-proxy-middleware: ^2.0.0 morgan: ^1.10.0 - msw: ^1.0.0 + msw: ^2.0.0 node-fetch: ^2.6.7 portfinder: ^1.0.32 supertest: ^6.1.3 From 02103becc6849f77ff08618d3c551797e0df7ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 May 2024 12:39:27 +0200 Subject: [PATCH 063/118] move over cache and database services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/six-llamas-give.md | 6 + packages/backend-app-api/api-report.md | 4 +- packages/backend-common/api-report.md | 47 ++- .../src/cache/CacheManager.test.ts | 384 ------------------ .../src/cache/cacheToPluginCacheManager.ts | 34 ++ packages/backend-common/src/cache/index.ts | 11 +- packages/backend-common/src/cache/reexport.ts | 29 ++ packages/backend-common/src/cache/types.ts | 37 +- packages/backend-common/src/database/index.ts | 7 +- .../backend-common/src/database/reexport.ts | 37 ++ .../src/discovery/HostDiscovery.ts | 9 +- .../backend-common/src/discovery/index.ts | 1 + packages/backend-defaults/api-report-cache.md | 33 +- .../backend-defaults/api-report-database.md | 37 ++ packages/backend-defaults/package.json | 13 +- .../entrypoints}/cache/CacheClient.test.ts | 0 .../src/entrypoints}/cache/CacheClient.ts | 0 .../cache/CacheManager.integration.test.ts | 39 +- .../src/entrypoints}/cache/CacheManager.ts | 63 +-- .../entrypoints/cache/cacheServiceFactory.ts | 7 +- .../src/entrypoints/cache/index.ts | 2 + .../src/entrypoints/cache/types.ts | 46 +++ .../database/DatabaseManager.test.ts | 0 .../entrypoints}/database/DatabaseManager.ts | 2 +- .../connectors/defaultNameOverride.test.ts | 0 .../connectors/defaultNameOverride.ts | 0 .../connectors/defaultSchemaOverride.test.ts | 0 .../connectors/defaultSchemaOverride.ts | 0 .../entrypoints}/database/connectors/index.ts | 0 .../connectors/mergeDatabaseConfig.test.ts | 0 .../connectors/mergeDatabaseConfig.ts | 0 .../database/connectors/mysql.test.ts | 0 .../entrypoints}/database/connectors/mysql.ts | 0 .../database/connectors/postgres.test.ts | 0 .../database/connectors/postgres.ts | 0 .../database/connectors/sqlite3.test.ts | 0 .../database/connectors/sqlite3.ts | 0 .../src/entrypoints/database/index.ts | 6 + .../src/entrypoints/database/types.ts | 100 +++++ plugins/auth-backend/api-report.md | 4 +- yarn.lock | 11 + 41 files changed, 469 insertions(+), 500 deletions(-) create mode 100644 .changeset/six-llamas-give.md delete mode 100644 packages/backend-common/src/cache/CacheManager.test.ts create mode 100644 packages/backend-common/src/cache/cacheToPluginCacheManager.ts create mode 100644 packages/backend-common/src/cache/reexport.ts create mode 100644 packages/backend-common/src/database/reexport.ts rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheClient.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheClient.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheManager.integration.test.ts (76%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheManager.ts (82%) create mode 100644 packages/backend-defaults/src/entrypoints/cache/types.ts rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/DatabaseManager.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/DatabaseManager.ts (99%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultNameOverride.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultNameOverride.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultSchemaOverride.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultSchemaOverride.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/index.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mergeDatabaseConfig.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mergeDatabaseConfig.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mysql.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mysql.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/postgres.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/postgres.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/sqlite3.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/sqlite3.ts (100%) create mode 100644 packages/backend-defaults/src/entrypoints/database/types.ts diff --git a/.changeset/six-llamas-give.md b/.changeset/six-llamas-give.md new file mode 100644 index 0000000000..9bfbf52625 --- /dev/null +++ b/.changeset/six-llamas-give.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': minor +'@backstage/backend-common': minor +--- + +Deprecated and moved over core services to `@backstage/backend-defaults` diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 49965f4c46..11d9dd90d4 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -8,7 +8,7 @@ import type { AppConfig } from '@backstage/config'; import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { CacheClient } from '@backstage/backend-common'; +import { CacheService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { ConfigSchema } from '@backstage/config-loader'; import { CorsOptions } from 'cors'; @@ -66,7 +66,7 @@ export interface Backend { } // @public @deprecated (undocumented) -export const cacheServiceFactory: () => ServiceFactory; +export const cacheServiceFactory: () => ServiceFactory; // @public (undocumented) export function createConfigSecretEnumerator(options: { diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 4e92420667..7e677bd570 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -17,11 +17,12 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import { BitbucketCloudIntegration } from '@backstage/integration'; import { BitbucketIntegration } from '@backstage/integration'; import { BitbucketServerIntegration } from '@backstage/integration'; -import { CacheService as CacheClient } from '@backstage/backend-plugin-api'; -import { CacheServiceOptions as CacheClientOptions } from '@backstage/backend-plugin-api'; -import { CacheServiceSetOptions as CacheClientSetOptions } from '@backstage/backend-plugin-api'; +import { CacheService } from '@backstage/backend-plugin-api'; +import { CacheServiceOptions } from '@backstage/backend-plugin-api'; +import type { CacheServiceSetOptions } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import cors from 'cors'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; import Docker from 'dockerode'; import { ErrorRequestHandler } from 'express'; import express from 'express'; @@ -44,7 +45,6 @@ import { LoggerService } from '@backstage/backend-plugin-api'; import { MergeResult } from 'isomorphic-git'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; -import { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api'; import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { PushResult } from 'isomorphic-git'; import { Readable } from 'stream'; @@ -193,18 +193,26 @@ export class BitbucketUrlReader implements UrlReader { toString(): string; } -export { CacheClient }; +// @public @deprecated (undocumented) +export type CacheClient = CacheService; -export { CacheClientOptions }; +// @public @deprecated (undocumented) +export type CacheClientOptions = CacheServiceOptions; -export { CacheClientSetOptions }; +// @public @deprecated (undocumented) +export type CacheClientSetOptions = CacheServiceSetOptions; // @public export class CacheManager { - forPlugin(pluginId: string): PluginCacheManager; + forPlugin(pluginId: string): { + getClient(options?: CacheServiceOptions): CacheService; + }; static fromConfig( config: Config, - options?: CacheManagerOptions, + options?: { + logger?: LoggerService; + onError?: (err: Error) => void; + }, ): CacheManager; } @@ -214,10 +222,10 @@ export type CacheManagerOptions = { onError?: (err: Error) => void; }; -// @public (undocumented) -export function cacheToPluginCacheManager( - cache: CacheClient, -): PluginCacheManager; +// @public +export function cacheToPluginCacheManager(cache: CacheService): { + getClient(options?: CacheServiceOptions): CacheService; +}; // @public @deprecated export const coloredFormat: winston.Logform.Format; @@ -575,10 +583,10 @@ export const legacyPlugin: ( default: LegacyCreateRouter< TransformedEnv< { - cache: CacheClient; + cache: CacheService; config: RootConfigService; database: PluginDatabaseManager; - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; logger: LoggerService; permissions: PermissionsService; scheduler: SchedulerService; @@ -588,7 +596,9 @@ export const legacyPlugin: ( }, { logger: (log: LoggerService) => Logger; - cache: (cache: CacheClient) => PluginCacheManager; + cache: (cache: CacheService) => { + getClient(options?: CacheServiceOptions | undefined): CacheService; + }; } > >; @@ -639,12 +649,13 @@ export function notFoundHandler(): RequestHandler; // @public (undocumented) export interface PluginCacheManager { // (undocumented) - getClient(options?: CacheClientOptions): CacheClient; + getClient(options?: CacheServiceOptions): CacheService; } export { PluginDatabaseManager }; -export { PluginEndpointDiscovery }; +// @public @deprecated (undocumented) +export type PluginEndpointDiscovery = DiscoveryService; // @public export interface PullOptions { diff --git a/packages/backend-common/src/cache/CacheManager.test.ts b/packages/backend-common/src/cache/CacheManager.test.ts deleted file mode 100644 index 4b7bb05efd..0000000000 --- a/packages/backend-common/src/cache/CacheManager.test.ts +++ /dev/null @@ -1,384 +0,0 @@ -/* - * 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 { ConfigReader } from '@backstage/config'; -import Keyv from 'keyv'; -import KeyvMemcache from '@keyv/memcache'; -import KeyvRedis from '@keyv/redis'; -import { DefaultCacheClient } from './CacheClient'; -import { CacheManager } from './CacheManager'; - -jest.createMockFromModule('keyv'); -jest.mock('keyv'); -jest.createMockFromModule('@keyv/memcache'); -jest.mock('@keyv/memcache'); -jest.createMockFromModule('@keyv/redis'); -jest.mock('@keyv/redis'); -jest.mock('./CacheClient', () => { - return { - DefaultCacheClient: jest.fn(), - }; -}); - -const globalDefaultTtl = 1234; -describe('CacheManager', () => { - const defaultConfigOptions = { - backend: { - cache: { - store: 'memory', - defaultTtl: globalDefaultTtl, - }, - }, - }; - const defaultConfig = () => new ConfigReader(defaultConfigOptions); - - afterEach(() => jest.resetAllMocks()); - - describe('CacheManager.fromConfig', () => { - it('accesses the backend.cache key', () => { - const getOptionalString = jest.fn(); - const getOptionalBoolean = jest.fn(); - const getOptionalNumber = jest.fn(); - const config = defaultConfig(); - config.getOptionalString = getOptionalString; - config.getOptionalBoolean = getOptionalBoolean; - config.getOptionalNumber = getOptionalNumber; - - CacheManager.fromConfig(config); - - expect(getOptionalString.mock.calls[0][0]).toEqual('backend.cache.store'); - expect(getOptionalString.mock.calls[1][0]).toEqual( - 'backend.cache.connection', - ); - expect(getOptionalBoolean.mock.calls[0][0]).toEqual( - 'backend.cache.useRedisSets', - ); - expect(getOptionalNumber.mock.calls[0][0]).toEqual( - 'backend.cache.defaultTtl', - ); - }); - - it('does not require the backend.cache key', () => { - const config = new ConfigReader({ backend: {} }); - expect(() => { - CacheManager.fromConfig(config); - }).not.toThrow(); - }); - - it('throws on unknown cache store', () => { - const config = new ConfigReader({ - backend: { cache: { store: 'notreal' } }, - }); - expect(() => { - CacheManager.fromConfig(config); - }).toThrow(); - }); - }); - - describe('CacheManager.forPlugin', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - - it('connects to a cache store scoped to the plugin', async () => { - const pluginId = 'test1'; - manager.forPlugin(pluginId).getClient(); - - const client = DefaultCacheClient as jest.Mock; - expect(client).toHaveBeenCalledTimes(1); - }); - - it('attaches error handler to client', () => { - const pluginId = 'error-test'; - manager.forPlugin(pluginId).getClient(); - - const client = DefaultCacheClient as jest.Mock; - const mockCalls = client.mock.calls.splice(-1); - const realClient = mockCalls[0][0] as Keyv; - expect(realClient.on).toHaveBeenCalledWith('error', expect.any(Function)); - }); - - it('provides different plugins different cache clients', async () => { - const plugin1Id = 'test1'; - const plugin2Id = 'test2'; - const expectedTtl = 3600; - manager.forPlugin(plugin1Id).getClient({ defaultTtl: expectedTtl }); - manager.forPlugin(plugin2Id).getClient({ defaultTtl: expectedTtl }); - - const client = DefaultCacheClient as jest.Mock; - const cache = Keyv as unknown as jest.Mock; - expect(cache).toHaveBeenCalledTimes(2); - expect(client).toHaveBeenCalledTimes(2); - - const plugin1CallArgs = cache.mock.calls[0]; - const plugin2CallArgs = cache.mock.calls[1]; - expect(plugin1CallArgs[0].namespace).not.toEqual( - plugin2CallArgs[0].namespace, - ); - }); - }); - - describe('CacheManager.forPlugin stores', () => { - it('returns memory client when no cache is configured', () => { - const manager = CacheManager.fromConfig( - new ConfigReader({ backend: {} }), - ); - const expectedTtl = 3600; - const expectedNamespace = 'test-plugin'; - manager - .forPlugin(expectedNamespace) - .getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCalls = cache.mock.calls.splice(-1); - const callArgs = mockCalls[0]; - expect(callArgs[0]).toMatchObject({ - ttl: expectedTtl, - namespace: expectedNamespace, - }); - }); - - it('returns memory client when explicitly configured', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - const expectedTtl = 3600; - const expectedNamespace = 'test-plugin'; - manager - .forPlugin(expectedNamespace) - .getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCalls = cache.mock.calls.splice(-1); - const callArgs = mockCalls[0]; - expect(callArgs[0]).toMatchObject({ - ttl: expectedTtl, - namespace: expectedNamespace, - }); - }); - - it('returns memory client with a global defaultTtl when explicitly configured', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - const expectedNamespace = 'test-plugin'; - manager.forPlugin(expectedNamespace).getClient(); - - const cache = Keyv as unknown as jest.Mock; - const mockCalls = cache.mock.calls.splice(-1); - const callArgs = mockCalls[0]; - expect(callArgs[0]).toMatchObject({ - ttl: globalDefaultTtl, - namespace: expectedNamespace, - }); - }); - - it('shares memory across multiple instances of the memory client', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - const plugin = 'test-plugin'; - - // Instantiate two in-memory clients. - manager.forPlugin(plugin).getClient({ defaultTtl: 10 }); - manager.forPlugin(plugin).getClient({ defaultTtl: 10 }); - - const cache = Keyv as unknown as jest.Mock; - const mockCall2 = cache.mock.calls.splice(-1)[0][0]; - const mockCall1 = cache.mock.calls.splice(-1)[0][0]; - - // Note: .toBe() checks referential identity of object instances. - expect(mockCall1.store).toBe(mockCall2.store); - }); - - it('returns a memcache client when configured', () => { - const expectedHost = '127.0.0.1:11211'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'memcache', - connection: expectedHost, - }, - }, - }), - ); - const expectedTtl = 3600; - manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: expectedTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvMemcache); - const memcache = KeyvMemcache as unknown as jest.Mock; - const mockMemcacheCalls = memcache.mock.calls.splice(-1); - expect(mockMemcacheCalls[0][0]).toEqual(expectedHost); - }); - - it('returns a memcache client with a global defaultTtl when configured', () => { - const expectedHost = '127.0.0.1:11211'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'memcache', - connection: expectedHost, - defaultTtl: globalDefaultTtl, - }, - }, - }), - ); - manager.forPlugin('test').getClient(); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: globalDefaultTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvMemcache); - const memcache = KeyvMemcache as unknown as jest.Mock; - const mockMemcacheCalls = memcache.mock.calls.splice(-1); - expect(mockMemcacheCalls[0][0]).toEqual(expectedHost); - }); - - it('returns a Redis client when configured', () => { - const redisConnection = 'redis://127.0.0.1:6379'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection: redisConnection, - }, - }, - }), - ); - const expectedTtl = 3600; - manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: expectedTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); - const redis = KeyvRedis as unknown as jest.Mock; - const mockRedisCalls = redis.mock.calls.splice(-1); - expect(mockRedisCalls[0][0]).toEqual(redisConnection); - }); - - it('returns a Redis client with a global defaultTtl when configured', () => { - const redisConnection = 'redis://127.0.0.1:6379'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection: redisConnection, - defaultTtl: globalDefaultTtl, - }, - }, - }), - ); - manager.forPlugin('test').getClient(); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: globalDefaultTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); - const redis = KeyvRedis as unknown as jest.Mock; - const mockRedisCalls = redis.mock.calls.splice(-1); - expect(mockRedisCalls[0][0]).toEqual(redisConnection); - }); - - it('returns a Redis client when configured with useRedisSets flag', () => { - const redisConnection = 'redis://127.0.0.1:6379'; - const useRedisSets = false; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection: redisConnection, - useRedisSets: useRedisSets, - }, - }, - }), - ); - const expectedTtl = 3600; - manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: expectedTtl, - useRedisSets: useRedisSets, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); - const redis = KeyvRedis as unknown as jest.Mock; - const mockRedisCalls = redis.mock.calls.splice(-1); - expect(mockRedisCalls[0][0]).toEqual(redisConnection); - }); - }); - - describe('connection errors', () => { - it('uses provided logger', () => { - // Set up and inject mock logger. - const mockLogger = { child: jest.fn(), error: jest.fn() }; - mockLogger.child.mockImplementation(() => mockLogger as any); - const manager = CacheManager.fromConfig(defaultConfig(), { - logger: mockLogger as any, - }); - - // Set up a cache client using the configured manager. - manager.forPlugin('error-logger-test').getClient(); - - // Retrieve the error handler attached to the cache client. - const client = DefaultCacheClient as jest.Mock; - const mockCalls = client.mock.calls.splice(-1); - const realClient = mockCalls[0][0] as Keyv; - const realOnError = realClient.on as jest.Mock; - const realHandler = realOnError.mock.calls.splice(-1)[0][1]; - - // Invoke the actual error handler. - const expectedError = new Error('some error'); - realHandler(expectedError); - expect(mockLogger.error).toHaveBeenCalledWith( - 'Failed to create cache client', - expectedError, - ); - }); - - it('calls provided handler', () => { - // Set up and inject mock logger. - const mockHandler = jest.fn(); - const manager = CacheManager.fromConfig(defaultConfig(), { - onError: mockHandler, - }); - - // Set up a cache client using the configured manager. - manager.forPlugin('error-handler-test').getClient(); - - // Retrieve the error handler attached to the cache client. - const client = DefaultCacheClient as jest.Mock; - const mockCalls = client.mock.calls.splice(-1); - const realClient = mockCalls[0][0] as Keyv; - const realOnError = realClient.on as jest.Mock; - const realHandler = realOnError.mock.calls.splice(-1)[0][1]; - - // Invoke the actual error handler. - const expectedError = new Error('some error'); - realHandler(expectedError); - expect(mockHandler).toHaveBeenCalledWith(expectedError); - }); - }); -}); diff --git a/packages/backend-common/src/cache/cacheToPluginCacheManager.ts b/packages/backend-common/src/cache/cacheToPluginCacheManager.ts new file mode 100644 index 0000000000..2654934804 --- /dev/null +++ b/packages/backend-common/src/cache/cacheToPluginCacheManager.ts @@ -0,0 +1,34 @@ +/* + * 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 { + CacheService, + CacheServiceOptions, +} from '@backstage/backend-plugin-api'; + +/** + * Compatibility wrapper for going from a new-backend cache service to the + * old-backend plugin cache manager. + * + * @public + */ +export function cacheToPluginCacheManager(cache: CacheService): { + getClient(options?: CacheServiceOptions): CacheService; +} { + return { + getClient: (opts: CacheServiceOptions) => cache.withOptions(opts), + }; +} diff --git a/packages/backend-common/src/cache/index.ts b/packages/backend-common/src/cache/index.ts index 0187aa45d5..9d05745ebd 100644 --- a/packages/backend-common/src/cache/index.ts +++ b/packages/backend-common/src/cache/index.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -export { CacheManager, cacheToPluginCacheManager } from './CacheManager'; -export type { - CacheClient, - CacheClientSetOptions, - PluginCacheManager, - CacheManagerOptions, - CacheClientOptions, -} from './types'; +export { cacheToPluginCacheManager } from './cacheToPluginCacheManager'; +export * from './reexport'; +export * from './types'; diff --git a/packages/backend-common/src/cache/reexport.ts b/packages/backend-common/src/cache/reexport.ts new file mode 100644 index 0000000000..9f6106de60 --- /dev/null +++ b/packages/backend-common/src/cache/reexport.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/* + * NOTE(freben): This is a temporary hack. We use cross-package imports so that + * we do not have to maintain double implementations for the time being, until + * backend-common is properly removed. + */ + +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +export { CacheManager } from '../../../backend-defaults/src/entrypoints/cache/CacheManager'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +export { + type PluginCacheManager, + type CacheManagerOptions, +} from '../../../backend-defaults/src/entrypoints/cache/types'; diff --git a/packages/backend-common/src/cache/types.ts b/packages/backend-common/src/cache/types.ts index 93fa0ac77c..83b6916843 100644 --- a/packages/backend-common/src/cache/types.ts +++ b/packages/backend-common/src/cache/types.ts @@ -14,39 +14,26 @@ * limitations under the License. */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { +import type { CacheService, + CacheServiceSetOptions, CacheServiceOptions, } from '@backstage/backend-plugin-api'; -export type { - CacheService as CacheClient, - CacheServiceSetOptions as CacheClientSetOptions, - CacheServiceOptions as CacheClientOptions, -} from '@backstage/backend-plugin-api'; - /** - * Options given when constructing a {@link CacheManager}. - * * @public + * @deprecated Use `CacheService` from the `@backstage/backend-plugin-api` package instead */ -export type CacheManagerOptions = { - /** - * An optional logger for use by the PluginCacheManager. - */ - logger?: LoggerService; - - /** - * An optional handler for connection errors emitted from the underlying data - * store. - */ - onError?: (err: Error) => void; -}; +export type CacheClient = CacheService; /** * @public + * @deprecated Use `CacheServiceSetOptions` from the `@backstage/backend-plugin-api` package instead */ -export interface PluginCacheManager { - getClient(options?: CacheServiceOptions): CacheService; -} +export type CacheClientSetOptions = CacheServiceSetOptions; + +/** + * @public + * @deprecated Use `CacheServiceOptions` from the `@backstage/backend-plugin-api` package instead + */ +export type CacheClientOptions = CacheServiceOptions; diff --git a/packages/backend-common/src/database/index.ts b/packages/backend-common/src/database/index.ts index 77d75653b8..0e95261d82 100644 --- a/packages/backend-common/src/database/index.ts +++ b/packages/backend-common/src/database/index.ts @@ -14,10 +14,5 @@ * limitations under the License. */ -export { DatabaseManager, dropDatabase } from './DatabaseManager'; -export type { - DatabaseManagerOptions, - LegacyRootDatabaseService, -} from './DatabaseManager'; - +export * from './reexport'; export type { PluginDatabaseManager } from './types'; diff --git a/packages/backend-common/src/database/reexport.ts b/packages/backend-common/src/database/reexport.ts new file mode 100644 index 0000000000..1ecff9be15 --- /dev/null +++ b/packages/backend-common/src/database/reexport.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/* + * NOTE(freben): This is a temporary hack. We use cross-package imports so that + * we do not have to maintain double implementations for the time being, until + * backend-common is properly removed. When it is, the impleemntation should be + * moved into this part of the repo instead. + */ + +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + DatabaseManager, + dropDatabase, + type DatabaseManagerOptions, + type LegacyRootDatabaseService, +} from '../../../backend-defaults/src/entrypoints/database/DatabaseManager'; + +export { + DatabaseManager, + dropDatabase, + type DatabaseManagerOptions, + type LegacyRootDatabaseService, +}; diff --git a/packages/backend-common/src/discovery/HostDiscovery.ts b/packages/backend-common/src/discovery/HostDiscovery.ts index cf0ddff611..77810b7f6d 100644 --- a/packages/backend-common/src/discovery/HostDiscovery.ts +++ b/packages/backend-common/src/discovery/HostDiscovery.ts @@ -15,8 +15,13 @@ */ import { HostDiscovery as _HostDiscovery } from '@backstage/backend-app-api'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; -export type { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api'; +/** + * @public + * @deprecated Use `DiscoveryService` from `@backstage/backend-plugin-api` instead + */ +export type PluginEndpointDiscovery = DiscoveryService; /** * HostDiscovery is a basic PluginEndpointDiscovery implementation @@ -40,6 +45,6 @@ export const HostDiscovery = _HostDiscovery; * resolved to the same host, so there won't be any balancing of internal traffic. * * @public - * @deprecated Use {@link HostDiscovery} instead + * @deprecated Use `HostDiscovery` from `@backstage/backend-defaults/discovery` instead */ export const SingleHostDiscovery = _HostDiscovery; diff --git a/packages/backend-common/src/discovery/index.ts b/packages/backend-common/src/discovery/index.ts index bad721d4da..827fd059ad 100644 --- a/packages/backend-common/src/discovery/index.ts +++ b/packages/backend-common/src/discovery/index.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { HostDiscovery, SingleHostDiscovery, diff --git a/packages/backend-defaults/api-report-cache.md b/packages/backend-defaults/api-report-cache.md index 150ed391f0..2cd2f3efc9 100644 --- a/packages/backend-defaults/api-report-cache.md +++ b/packages/backend-defaults/api-report-cache.md @@ -3,11 +3,40 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { CacheClient } from '@backstage/backend-common'; +import { CacheService } from '@backstage/backend-plugin-api'; +import { CacheServiceOptions } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +// @public +export class CacheManager { + forPlugin(pluginId: string): { + getClient(options?: CacheServiceOptions): CacheService; + }; + static fromConfig( + config: Config, + options?: { + logger?: LoggerService; + onError?: (err: Error) => void; + }, + ): CacheManager; +} + +// @public +export type CacheManagerOptions = { + logger?: LoggerService; + onError?: (err: Error) => void; +}; + // @public (undocumented) -export const cacheServiceFactory: () => ServiceFactory; +export const cacheServiceFactory: () => ServiceFactory; + +// @public (undocumented) +export interface PluginCacheManager { + // (undocumented) + getClient(options?: CacheServiceOptions): CacheService; +} // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-database.md b/packages/backend-defaults/api-report-database.md index 512e1febc9..0edadaad59 100644 --- a/packages/backend-defaults/api-report-database.md +++ b/packages/backend-defaults/api-report-database.md @@ -3,14 +3,51 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { Config } from '@backstage/config'; +import { DatabaseService } from '@backstage/backend-plugin-api'; +import { LifecycleService } from '@backstage/backend-plugin-api'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginDatabaseManager } from '@backstage/backend-common'; +import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +// @public +export class DatabaseManager implements LegacyRootDatabaseService { + forPlugin( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): DatabaseService; + static fromConfig( + config: Config, + options?: DatabaseManagerOptions, + ): DatabaseManager; +} + +// @public +export type DatabaseManagerOptions = { + migrations?: DatabaseService['migrations']; + logger?: LoggerService; +}; + // @public (undocumented) export const databaseServiceFactory: () => ServiceFactory< PluginDatabaseManager, 'plugin' >; +// @public +export function dropDatabase( + dbConfig: Config, + ...databaseNames: string[] +): Promise; + +// @public +export type LegacyRootDatabaseService = { + forPlugin(pluginId: string): DatabaseService; +}; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index e4db289e33..f1eb7e0718 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", - "description": "Backend defaults used by Backstage backend apps", "version": "0.2.19-next.0", + "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" }, @@ -84,6 +84,7 @@ "dependencies": { "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", + "@backstage/backend-dev-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/config-loader": "workspace:^", @@ -91,12 +92,22 @@ "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", "@backstage/types": "workspace:^", + "@keyv/memcache": "^1.3.5", + "@keyv/redis": "^2.5.3", "@opentelemetry/api": "^1.3.0", + "better-sqlite3": "^9.0.0", "cron": "^3.0.0", + "fs-extra": "^11.2.0", + "keyv": "^4.5.2", "knex": "^3.0.0", "lodash": "^4.17.21", "luxon": "^3.0.0", + "mysql2": "^3.0.0", + "p-limit": "^3.1.0", + "pg": "^8.11.3", + "pg-connection-string": "^2.3.0", "uuid": "^9.0.0", + "yn": "^4.0.0", "zod": "^3.22.4" }, "devDependencies": { diff --git a/packages/backend-common/src/cache/CacheClient.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheClient.test.ts similarity index 100% rename from packages/backend-common/src/cache/CacheClient.test.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheClient.test.ts diff --git a/packages/backend-common/src/cache/CacheClient.ts b/packages/backend-defaults/src/entrypoints/cache/CacheClient.ts similarity index 100% rename from packages/backend-common/src/cache/CacheClient.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheClient.ts diff --git a/packages/backend-common/src/cache/CacheManager.integration.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts similarity index 76% rename from packages/backend-common/src/cache/CacheManager.integration.test.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts index 7dc60a9051..a10b02a62e 100644 --- a/packages/backend-common/src/cache/CacheManager.integration.test.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/config'; -import { CacheManager } from './CacheManager'; +import { mockServices } from '@backstage/backend-test-utils'; import KeyvRedis from '@keyv/redis'; +import { CacheManager } from './CacheManager'; // This test is in a separate file because the main test file uses other mocking // that might interfere with this one. @@ -24,24 +24,27 @@ import KeyvRedis from '@keyv/redis'; // Contrived code because it's hard to spy on a default export jest.mock('@keyv/redis', () => { const ActualKeyvRedis = jest.requireActual('@keyv/redis'); - return jest - .fn() - .mockImplementation((...args: any[]) => new ActualKeyvRedis(...args)); + return jest.fn((...args: any[]) => { + return new ActualKeyvRedis(...args); + }); }); describe('CacheManager integration', () => { describe('redis', () => { it('only creates one underlying connection', async () => { + const connection = + process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING; + if (!connection) { + return; + } + const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - // no actual connection errors will be seen since we don't interact with it - connection: 'redis://localhost:6379', - }, + mockServices.rootConfig({ + data: { + backend: { cache: { store: 'redis', connection } }, }, }), + { onError: e => expect(e).not.toBeDefined() }, ); manager.forPlugin('p1').getClient(); @@ -56,20 +59,18 @@ describe('CacheManager integration', () => { // TODO(freben): This could be frameworkified as TestCaches just like // TestDatabases, but that will have to come some other day const connection = - process.env.BACKSTAGE_TEST_CACHE_REDIS_CONNECTION_STRING; + process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING; if (!connection) { return; } const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection, - }, + mockServices.rootConfig({ + data: { + backend: { cache: { store: 'redis', connection } }, }, }), + { onError: e => expect(e).not.toBeDefined() }, ); const plugin1 = manager.forPlugin('p1').getClient(); diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts similarity index 82% rename from packages/backend-common/src/cache/CacheManager.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheManager.ts index 8af742be8c..70f952ea46 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts @@ -14,21 +14,25 @@ * limitations under the License. */ -import { Config } from '@backstage/config'; -import Keyv from 'keyv'; -import KeyvMemcache from '@keyv/memcache'; -import KeyvRedis from '@keyv/redis'; import { CacheService, CacheServiceOptions, LoggerService, } from '@backstage/backend-plugin-api'; -import { getRootLogger } from '../logging'; +import { Config } from '@backstage/config'; +import Keyv from 'keyv'; import { DefaultCacheClient } from './CacheClient'; -import { CacheManagerOptions, PluginCacheManager } from './types'; +import { CacheManagerOptions } from './types'; type StoreFactory = (pluginId: string, defaultTtl: number | undefined) => Keyv; +/* + * TODO(freben): This class intentionally inlines the CacheManagerOptions and + * PluginCacheManager types, to not break the api reports in backend-common + * which re-exports it. When backend-common is deprecated, we can stop inlining + * those types. + */ + /** * Implements a Cache Manager which will automatically create new cache clients * for plugins when requested. All requested cache clients are created with the @@ -47,7 +51,7 @@ export class CacheManager { memory: this.createMemoryStoreFactory(), }; - private readonly logger: LoggerService; + private readonly logger?: LoggerService; private readonly store: keyof CacheManager['storeFactories']; private readonly connection: string; private readonly useRedisSets: boolean; @@ -62,7 +66,18 @@ export class CacheManager { */ static fromConfig( config: Config, - options: CacheManagerOptions = {}, + options: { + /** + * An optional logger for use by the PluginCacheManager. + */ + logger?: LoggerService; + + /** + * An optional handler for connection errors emitted from the underlying data + * store. + */ + onError?: (err: Error) => void; + } = {}, ): CacheManager { // If no `backend.cache` config is provided, instantiate the CacheManager // with an in-memory cache client. @@ -72,27 +87,26 @@ export class CacheManager { config.getOptionalString('backend.cache.connection') || ''; const useRedisSets = config.getOptionalBoolean('backend.cache.useRedisSets') ?? true; - - // TODO: Make logger required and remove the default logger after moving this class to the `backstage-defaults`package - const logger = (options.logger || getRootLogger()).child({ + const logger = options.logger?.child({ type: 'cacheManager', }); return new CacheManager( store, connectionString, useRedisSets, - logger, options.onError, + logger, defaultTtl, ); } - private constructor( + /** @internal */ + constructor( store: string, connectionString: string, useRedisSets: boolean, - logger: LoggerService, errorHandler: CacheManagerOptions['onError'], + logger?: LoggerService, defaultTtl?: number, ) { if (!this.storeFactories.hasOwnProperty(store)) { @@ -112,7 +126,9 @@ export class CacheManager { * @param pluginId - The plugin that the cache manager should be created for. * Plugin names should be unique. */ - forPlugin(pluginId: string): PluginCacheManager { + forPlugin(pluginId: string): { + getClient(options?: CacheServiceOptions): CacheService; + } { return { getClient: (defaultOptions = {}) => { const clientFactory = (options: CacheServiceOptions) => { @@ -124,7 +140,7 @@ export class CacheManager { // Always provide an error handler to avoid stopping the process. concreteClient.on('error', (err: Error) => { // In all cases, just log the error. - this.logger.error('Failed to create cache client', err); + this.logger?.error('Failed to create cache client', err); // Invoke any custom error handler if provided. if (typeof this.errorHandler === 'function') { @@ -149,7 +165,8 @@ export class CacheManager { } private createRedisStoreFactory(): StoreFactory { - let store: KeyvRedis | undefined; + const KeyvRedis = require('@keyv/redis'); + let store: typeof KeyvRedis | undefined; return (pluginId, defaultTtl) => { if (!store) { store = new KeyvRedis(this.connection); @@ -164,7 +181,8 @@ export class CacheManager { } private createMemcacheStoreFactory(): StoreFactory { - let store: KeyvMemcache | undefined; + const KeyvMemcache = require('@keyv/memcache'); + let store: typeof KeyvMemcache | undefined; return (pluginId, defaultTtl) => { if (!store) { store = new KeyvMemcache(this.connection); @@ -187,12 +205,3 @@ export class CacheManager { }); } } - -/** @public */ -export function cacheToPluginCacheManager( - cache: CacheService, -): PluginCacheManager { - return { - getClient: (opts: CacheServiceOptions) => cache.withOptions(opts), - }; -} diff --git a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts index d348c455d2..f60d770644 100644 --- a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { CacheManager } from '@backstage/backend-common'; import { coreServices, createServiceFactory, } from '@backstage/backend-plugin-api'; +import { CacheManager } from './CacheManager'; /** * @public @@ -28,9 +28,10 @@ export const cacheServiceFactory = createServiceFactory({ deps: { config: coreServices.rootConfig, plugin: coreServices.pluginMetadata, + logger: coreServices.rootLogger, }, - async createRootContext({ config }) { - return CacheManager.fromConfig(config); + async createRootContext({ config, logger }) { + return CacheManager.fromConfig(config, { logger }); }, async factory({ plugin }, manager) { return manager.forPlugin(plugin.getId()).getClient(); diff --git a/packages/backend-defaults/src/entrypoints/cache/index.ts b/packages/backend-defaults/src/entrypoints/cache/index.ts index f96ee77182..b16fa56bd2 100644 --- a/packages/backend-defaults/src/entrypoints/cache/index.ts +++ b/packages/backend-defaults/src/entrypoints/cache/index.ts @@ -15,3 +15,5 @@ */ export { cacheServiceFactory } from './cacheServiceFactory'; +export { CacheManager } from './CacheManager'; +export type { CacheManagerOptions, PluginCacheManager } from './types'; diff --git a/packages/backend-defaults/src/entrypoints/cache/types.ts b/packages/backend-defaults/src/entrypoints/cache/types.ts new file mode 100644 index 0000000000..ccb3ed5f6d --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/cache/types.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2020 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 { LoggerService } from '@backstage/backend-plugin-api'; +import { + CacheService, + CacheServiceOptions, +} from '@backstage/backend-plugin-api'; + +/** + * Options given when constructing a {@link CacheManager}. + * + * @public + */ +export type CacheManagerOptions = { + /** + * An optional logger for use by the PluginCacheManager. + */ + logger?: LoggerService; + + /** + * An optional handler for connection errors emitted from the underlying data + * store. + */ + onError?: (err: Error) => void; +}; + +/** + * @public + */ +export interface PluginCacheManager { + getClient(options?: CacheServiceOptions): CacheService; +} diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts similarity index 100% rename from packages/backend-common/src/database/DatabaseManager.test.ts rename to packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts similarity index 99% rename from packages/backend-common/src/database/DatabaseManager.ts rename to packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts index dd36cc3b4a..f19a848056 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts @@ -41,7 +41,7 @@ function pluginPath(pluginId: string): string { * @public */ export type DatabaseManagerOptions = { - migrations?: PluginDatabaseManager['migrations']; + migrations?: DatabaseService['migrations']; logger?: LoggerService; }; diff --git a/packages/backend-common/src/database/connectors/defaultNameOverride.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultNameOverride.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.test.ts diff --git a/packages/backend-common/src/database/connectors/defaultNameOverride.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultNameOverride.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.ts diff --git a/packages/backend-common/src/database/connectors/defaultSchemaOverride.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultSchemaOverride.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.test.ts diff --git a/packages/backend-common/src/database/connectors/defaultSchemaOverride.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultSchemaOverride.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.ts diff --git a/packages/backend-common/src/database/connectors/index.ts b/packages/backend-defaults/src/entrypoints/database/connectors/index.ts similarity index 100% rename from packages/backend-common/src/database/connectors/index.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/index.ts diff --git a/packages/backend-common/src/database/connectors/mergeDatabaseConfig.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mergeDatabaseConfig.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.test.ts diff --git a/packages/backend-common/src/database/connectors/mergeDatabaseConfig.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mergeDatabaseConfig.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.ts diff --git a/packages/backend-common/src/database/connectors/mysql.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mysql.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mysql.test.ts diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mysql.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts diff --git a/packages/backend-common/src/database/connectors/postgres.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/postgres.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/postgres.test.ts diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts similarity index 100% rename from packages/backend-common/src/database/connectors/postgres.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts diff --git a/packages/backend-common/src/database/connectors/sqlite3.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/sqlite3.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts diff --git a/packages/backend-common/src/database/connectors/sqlite3.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts similarity index 100% rename from packages/backend-common/src/database/connectors/sqlite3.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts diff --git a/packages/backend-defaults/src/entrypoints/database/index.ts b/packages/backend-defaults/src/entrypoints/database/index.ts index d676c8013e..7d6221b856 100644 --- a/packages/backend-defaults/src/entrypoints/database/index.ts +++ b/packages/backend-defaults/src/entrypoints/database/index.ts @@ -15,3 +15,9 @@ */ export { databaseServiceFactory } from './databaseServiceFactory'; +export { + DatabaseManager, + type DatabaseManagerOptions, + type LegacyRootDatabaseService, + dropDatabase, +} from './DatabaseManager'; diff --git a/packages/backend-defaults/src/entrypoints/database/types.ts b/packages/backend-defaults/src/entrypoints/database/types.ts new file mode 100644 index 0000000000..a9cceaa1f9 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/database/types.ts @@ -0,0 +1,100 @@ +/* + * Copyright 2020 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 { + LifecycleService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { Knex } from 'knex'; + +export type { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; + +/** + * Manages an underlying Knex database driver. + */ +export interface DatabaseConnector { + /** + * Provides an instance of a knex database connector. + */ + createClient( + dbConfig: Config, + overrides?: Partial, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Knex; + + /** + * Provides a partial knex config sufficient to override a database name. + */ + createNameOverride(name: string): Partial; + + /** + * Provides a partial knex config sufficient to override a PostgreSQL schema + * name within utilizing the `searchPath` knex configuration. + */ + createSchemaOverride?(name: string): Partial; + + /** + * Produces a knex connection config object representing a database connection + * string. + */ + parseConnectionString( + connectionString: string, + client?: string, + ): Knex.StaticConnectionConfig; + + /** + * Performs a side-effect to ensure database names passed in are present. + * + * Calling this function on databases which already exist should do nothing. + * Missing databases should be created if needed. + */ + ensureDatabaseExists?( + dbConfig: Config, + ...databases: Array + ): Promise; + + /** + * Performs a side-effect to ensure schema names passed in are present. + * + * Calling this function on schemas which already exist should do nothing. + * Missing schemas should be created if needed. + */ + ensureSchemaExists?( + dbConfig: Config, + ...schemas: Array + ): Promise; + + /** + * Deletes databases. + */ + dropDatabase?(dbConfig: Config, ...databases: Array): Promise; +} + +export interface Connector { + getClient( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Promise; + + dropDatabase(...databaseNames: string[]): Promise; +} diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index edd462b528..d15399d279 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -14,7 +14,7 @@ import { AwsAlbResult as AwsAlbResult_2 } from '@backstage/plugin-auth-backend-m import { AzureEasyAuthResult } from '@backstage/plugin-auth-backend-module-azure-easyauth-provider'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { BackstageSignInResult } from '@backstage/plugin-auth-node'; -import { CacheClient } from '@backstage/backend-common'; +import { CacheService } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { ClientAuthResponse } from '@backstage/plugin-auth-node'; import { cloudflareAccessSignInResolvers } from '@backstage/plugin-auth-backend-module-cloudflare-access-provider'; @@ -452,7 +452,7 @@ export const providers: Readonly<{ signIn: { resolver: SignInResolver_2; }; - cache?: CacheClient | undefined; + cache?: CacheService | undefined; }) => AuthProviderFactory_2; resolvers: Readonly; }>; diff --git a/yarn.lock b/yarn.lock index d006daa23c..157f44465e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3430,6 +3430,7 @@ __metadata: dependencies: "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" + "@backstage/backend-dev-utils": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -3439,13 +3440,23 @@ __metadata: "@backstage/plugin-events-node": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" "@backstage/types": "workspace:^" + "@keyv/memcache": ^1.3.5 + "@keyv/redis": ^2.5.3 "@opentelemetry/api": ^1.3.0 + better-sqlite3: ^9.0.0 cron: ^3.0.0 + fs-extra: ^11.2.0 + keyv: ^4.5.2 knex: ^3.0.0 lodash: ^4.17.21 luxon: ^3.0.0 + mysql2: ^3.0.0 + p-limit: ^3.1.0 + pg: ^8.11.3 + pg-connection-string: ^2.3.0 uuid: ^9.0.0 wait-for-expect: ^3.0.2 + yn: ^4.0.0 zod: ^3.22.4 languageName: unknown linkType: soft From 4728a59c3d185d4606a2f962d8abe5ed187fb30b Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 22 May 2024 08:54:24 -0500 Subject: [PATCH 064/118] Task Schedule Definition Deprecation Correction Signed-off-by: Andre Wanlin --- packages/backend-tasks/src/tasks/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts index 955909e040..e6a31873be 100644 --- a/packages/backend-tasks/src/tasks/types.ts +++ b/packages/backend-tasks/src/tasks/types.ts @@ -157,7 +157,7 @@ export interface TaskScheduleDefinition { * that control the scheduling of a task. * * @public - * @deprecated Please import `SchedulerServiceTaskDefinitionConfig` from `@backstage/backend-plugin-api` instead + * @deprecated Please import `SchedulerServiceTaskScheduleDefinitionConfig` from `@backstage/backend-plugin-api` instead */ export interface TaskScheduleDefinitionConfig { /** From ed473cd98c2cfca94b0e496f7ceef85f135d27e2 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 22 May 2024 08:56:41 -0500 Subject: [PATCH 065/118] Added changeset Signed-off-by: Andre Wanlin --- .changeset/forty-adults-roll.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/forty-adults-roll.md diff --git a/.changeset/forty-adults-roll.md b/.changeset/forty-adults-roll.md new file mode 100644 index 0000000000..50dd60da9e --- /dev/null +++ b/.changeset/forty-adults-roll.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Updated the `TaskScheduleDefinitionConfig` deprecated comment to point to `SchedulerServiceTaskScheduleDefinitionConfig` From 9e63318311be4a267adcb7b4b566f69357df73cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 7 May 2024 08:30:04 +0200 Subject: [PATCH 066/118] Implement the scope feature of external access service tokens, as per BEP-0007 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/friendly-keys-fold.md | 6 + .changeset/great-cougars-guess.md | 5 + .changeset/neat-rivers-share.md | 5 + packages/backend-app-api/config.d.ts | 80 ++++++++ .../auth/DefaultAuthService.ts | 22 ++- .../auth/authServiceFactory.test.ts | 60 +++++- .../auth/external/ExternalTokenHandler.ts | 12 +- .../auth/external/helpers.test.ts | 181 ++++++++++++++++++ .../implementations/auth/external/helpers.ts | 144 ++++++++++++++ .../auth/external/legacy.test.ts | 150 +++++++++++---- .../implementations/auth/external/legacy.ts | 39 +++- .../auth/external/static.test.ts | 86 ++++++--- .../implementations/auth/external/static.ts | 28 ++- .../implementations/auth/external/types.ts | 14 +- .../services/implementations/auth/helpers.ts | 3 + .../permissions/permissionsServiceFactory.ts | 4 +- packages/backend-plugin-api/api-report.md | 10 + .../src/services/definitions/AuthService.ts | 49 +++++ .../src/services/definitions/index.ts | 1 + packages/backend-test-utils/api-report.md | 2 + .../src/next/services/mockCredentials.test.ts | 10 + .../src/next/services/mockCredentials.ts | 10 +- plugins/permission-node/api-report.md | 1 + .../src/ServerPermissionClient.test.ts | 66 ++++++- .../src/ServerPermissionClient.ts | 65 ++++++- 25 files changed, 968 insertions(+), 85 deletions(-) create mode 100644 .changeset/friendly-keys-fold.md create mode 100644 .changeset/great-cougars-guess.md create mode 100644 .changeset/neat-rivers-share.md create mode 100644 packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts create mode 100644 packages/backend-app-api/src/services/implementations/auth/external/helpers.ts diff --git a/.changeset/friendly-keys-fold.md b/.changeset/friendly-keys-fold.md new file mode 100644 index 0000000000..218abe4efe --- /dev/null +++ b/.changeset/friendly-keys-fold.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-plugin-api': patch +'@backstage/backend-app-api': patch +--- + +Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions. diff --git a/.changeset/great-cougars-guess.md b/.changeset/great-cougars-guess.md new file mode 100644 index 0000000000..af1de3e8ce --- /dev/null +++ b/.changeset/great-cougars-guess.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Made it possible to give access restrictions to `mockCredentials.service` diff --git a/.changeset/neat-rivers-share.md b/.changeset/neat-rivers-share.md new file mode 100644 index 0000000000..42649de977 --- /dev/null +++ b/.changeset/neat-rivers-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-node': patch +--- + +Ensure that service token access restrictions, when present, are taken into account diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index 5517af4f98..5b72fef54c 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -88,6 +88,46 @@ export interface Config { */ subject: string; }; + /** + * Restricts what types of access that are permitted for this access + * method. If no access restrictions are given, it'll have unlimited + * access. This access restriction applies for the framework level; + * individual plugins may have their own access control mechanisms + * on top of this. + */ + accessRestrictions?: Array<{ + /** + * Permit access to make requests to this plugin. + * + * Can be further refined by setting additional fields below. + */ + plugin: string; + /** + * If given, this method is limited to only performing actions + * with these named permissions in this plugin. + * + * Note that this only applies where permissions checks are + * enabled in the first place. Endpoints that are not protected by + * the permissions system at all, are not affected by this + * setting. + */ + permission?: string | Array; + /** + * If given, this method is limited to only performing actions + * whose permissions have these attributes. + * + * Note that this only applies where permissions checks are + * enabled in the first place. Endpoints that are not protected by + * the permissions system at all, are not affected by this + * setting. + */ + permissionAttribute?: { + /** + * One of more of 'create', 'read', 'update', or 'delete'. + */ + action?: string | Array; + }; + }>; } | { /** @@ -130,6 +170,46 @@ export interface Config { */ subject: string; }; + /** + * Restricts what types of access that are permitted for this access + * method. If no access restrictions are given, it'll have unlimited + * access. This access restriction applies for the framework level; + * individual plugins may have their own access control mechanisms + * on top of this. + */ + accessRestrictions?: Array<{ + /** + * Permit access to make requests to this plugin. + * + * Can be further refined by setting additional fields below. + */ + plugin: string; + /** + * If given, this method is limited to only performing actions + * with these named permissions in this plugin. + * + * Note that this only applies where permissions checks are + * enabled in the first place. Endpoints that are not protected by + * the permissions system at all, are not affected by this + * setting. + */ + permission?: string | Array; + /** + * If given, this method is limited to only performing actions + * whose permissions have these attributes. + * + * Note that this only applies where permissions checks are + * enabled in the first place. Endpoints that are not protected by + * the permissions system at all, are not affected by this + * setting. + */ + permissionAttribute?: { + /** + * One of more of 'create', 'read', 'update', or 'delete'. + */ + action?: string | Array; + }; + }>; } | { /** diff --git a/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts b/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts index dc76a8761b..d0c11a4eee 100644 --- a/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts +++ b/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts @@ -23,7 +23,11 @@ import { BackstageServicePrincipal, BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; -import { AuthenticationError, ForwardedError } from '@backstage/errors'; +import { + AuthenticationError, + ForwardedError, + NotAllowedError, +} from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import { decodeJwt } from 'jose'; import { ExternalTokenHandler } from './external/ExternalTokenHandler'; @@ -82,7 +86,21 @@ export class DefaultAuthService implements AuthService { const externalResult = await this.externalTokenHandler.verifyToken(token); if (externalResult) { - return createCredentialsWithServicePrincipal(externalResult.subject); + const restrictions = externalResult.accessRestrictions; + if (restrictions) { + if (!restrictions.has(this.pluginId)) { + const valid = [...restrictions.keys()].map(k => `'${k}'`).join(', '); + throw new NotAllowedError( + `This token's access is restricted to plugin(s) ${valid}`, + ); + } + } + + return createCredentialsWithServicePrincipal( + externalResult.subject, + undefined, + restrictions?.get(this.pluginId), + ); } throw new AuthenticationError('Illegal token'); diff --git a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts index 96d4ce4dc9..71fbe64963 100644 --- a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts @@ -42,7 +42,26 @@ const mockDeps = [ data: { backend: { baseUrl: 'http://localhost', - auth: { keys: [{ secret: 'abc' }] }, + auth: { + keys: [{ secret: 'abc' }], + externalAccess: [ + { + type: 'static', + options: { + token: 'limited-static-token', + subject: 'limited-static-subject', + }, + accessRestrictions: [{ plugin: 'catalog', permission: 'do.it' }], + }, + { + type: 'static', + options: { + token: 'unlimited-static-token', + subject: 'unlimited-static-subject', + }, + }, + ], + }, }, }, }), @@ -385,4 +404,43 @@ describe('authServiceFactory', () => { "Unable to call 'kubernetes' plugin on behalf of user, because the target plugin does not support on-behalf-of tokens or the plugin doesn't exist", ); }); + + it('should eagerly reject access to external access tokens based on plugin id', async () => { + const tester = ServiceFactoryTester.from(authServiceFactory, { + dependencies: mockDeps, + }); + + const catalogAuth = await tester.get('catalog'); + + await expect( + catalogAuth.authenticate('limited-static-token'), + ).resolves.toMatchObject({ + principal: { + subject: 'limited-static-subject', + accessRestrictions: { permissionNames: ['do.it'] }, + }, + }); + + await expect( + catalogAuth.authenticate('unlimited-static-token'), + ).resolves.toMatchObject({ + principal: { + subject: 'unlimited-static-subject', + }, + }); + + const scaffolderAuth = await tester.get('scaffolder'); + + await expect( + scaffolderAuth.authenticate('limited-static-token'), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"This token's access is restricted to plugin(s) 'catalog'"`, + ); + + await expect( + scaffolderAuth.authenticate('unlimited-static-token'), + ).resolves.toMatchObject({ + principal: { subject: 'unlimited-static-subject' }, + }); + }); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts index 79ad8dd3c4..82603953d0 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts @@ -20,8 +20,8 @@ import { } from '@backstage/backend-plugin-api'; import { LegacyTokenHandler } from './legacy'; import { StaticTokenHandler } from './static'; -import { TokenHandler } from './types'; import { JWKSHandler } from './jwks'; +import { AccessRestriptionsMap, TokenHandler } from './types'; const NEW_CONFIG_KEY = 'backend.auth.externalAccess'; const OLD_CONFIG_KEY = 'backend.auth.keys'; @@ -61,7 +61,7 @@ export class ExternalTokenHandler { `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`, ); } - handler.add(handlerConfig.getConfig('options')); + handler.add(handlerConfig); } // Load the old keys too @@ -80,7 +80,13 @@ export class ExternalTokenHandler { constructor(private readonly handlers: TokenHandler[]) {} - async verifyToken(token: string): Promise<{ subject: string } | undefined> { + async verifyToken(token: string): Promise< + | { + subject: string; + accessRestrictions?: AccessRestriptionsMap; + } + | undefined + > { for (const handler of this.handlers) { const result = await handler.verifyToken(token); if (result) { diff --git a/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts new file mode 100644 index 0000000000..ee0dc3cb87 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts @@ -0,0 +1,181 @@ +/* + * 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. + */ + +import { ConfigReader } from '@backstage/config'; +import { readAccessRestrictionsFromConfig } from './helpers'; +import { JsonObject } from '@backstage/types'; + +describe('readAccessRestrictionsFromConfig', () => { + function r(config: JsonObject) { + return readAccessRestrictionsFromConfig(new ConfigReader(config)); + } + + it('handles empty / missing restrictions', () => { + expect(r({})).toBeUndefined(); + expect(r({ accessRestrictions: [] })).toBeUndefined(); + }); + + it('handles type errors', () => { + expect(() => + r({ accessRestrictions: 7 }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions' in 'mock-config', got number, wanted object-array"`, + ); + expect(() => + r({ accessRestrictions: ['hello'] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0]' in 'mock-config', got string, wanted object-array"`, + ); + expect(() => + r({ accessRestrictions: [{ unknown: {} }] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid key 'unknown' in 'accessRestrictions' config, expected one of 'plugin', 'permission', 'permissionAttribute'"`, + ); + expect(() => + r({ accessRestrictions: [{ plugin: 7 }] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].plugin' in 'mock-config', got number, wanted string"`, + ); + expect(() => + r({ accessRestrictions: [{ plugin: 'valid', permission: 7 }] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].permission' in 'mock-config', got number, wanted string"`, + ); + expect(() => + r({ accessRestrictions: [{ plugin: 'valid', permission: [7] }] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].permission[0]' in 'mock-config', got number, wanted string-array"`, + ); + expect(() => + r({ accessRestrictions: [{ plugin: 'valid', permissionAttribute: 7 }] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].permissionAttribute' in 'mock-config', got number, wanted object"`, + ); + expect(() => + r({ + accessRestrictions: [ + { plugin: 'valid', permissionAttribute: { a: [] } }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid key 'a' in 'permissionAttribute' config, expected 'action'"`, + ); + expect(() => + r({ + accessRestrictions: [ + { plugin: 'valid', permissionAttribute: { action: 7 } }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].permissionAttribute.action' in 'mock-config', got number, wanted string"`, + ); + expect(() => + r({ + accessRestrictions: [ + { plugin: 'valid', permissionAttribute: { action: 'wrong' } }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid value 'wrong' at 'action' in 'permissionAttributes' config, valid values are 'create', 'read', 'update', 'delete'"`, + ); + }); + + it('parses valid access restrictions', () => { + expect( + r({ + accessRestrictions: [ + { + plugin: 'a', + }, + ], + }), + ).toEqual( + new Map( + Object.entries({ + a: {}, + }), + ), + ); + + expect( + r({ + accessRestrictions: [ + { + plugin: 'a', + permission: 'a, b a', + }, + ], + }), + ).toEqual( + new Map( + Object.entries({ + a: { permissionNames: ['a', 'b'] }, + }), + ), + ); + + expect( + r({ + accessRestrictions: [ + { + plugin: 'a', + permission: ['a', 'b', 'a'], + }, + ], + }), + ).toEqual( + new Map( + Object.entries({ + a: { permissionNames: ['a', 'b'] }, + }), + ), + ); + + expect( + r({ + accessRestrictions: [ + { + plugin: 'a', + permissionAttribute: { action: 'read, update read' }, + }, + ], + }), + ).toEqual( + new Map( + Object.entries({ + a: { permissionAttributes: { action: ['read', 'update'] } }, + }), + ), + ); + + expect( + r({ + accessRestrictions: [ + { + plugin: 'a', + permissionAttribute: { action: ['read', 'update', 'read'] }, + }, + ], + }), + ).toEqual( + new Map( + Object.entries({ + a: { permissionAttributes: { action: ['read', 'update'] } }, + }), + ), + ); + }); +}); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts b/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts new file mode 100644 index 0000000000..5d199a5067 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts @@ -0,0 +1,144 @@ +/* + * 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. + */ + +import { Config } from '@backstage/config'; +import { AccessRestriptionsMap } from './types'; + +/** + * Parses and returns the `accessRestrictions` configuration from an + * `externalAccess` entry, or undefined if there wasn't one. + * + * @internal + */ +export function readAccessRestrictionsFromConfig( + externalAccessEntryConfig: Config, +): AccessRestriptionsMap | undefined { + const configs = + externalAccessEntryConfig.getOptionalConfigArray('accessRestrictions') ?? + []; + + const result: AccessRestriptionsMap = new Map(); + for (const config of configs) { + const validKeys = ['plugin', 'permission', 'permissionAttribute']; + for (const key of config.keys()) { + if (!validKeys.includes(key)) { + const valid = validKeys.map(k => `'${k}'`).join(', '); + throw new Error( + `Invalid key '${key}' in 'accessRestrictions' config, expected one of ${valid}`, + ); + } + } + + const pluginId = config.getString('plugin'); + const permissionNames = readPermissionNames(config); + const permissionAttributes = readPermissionAttributes(config); + + if (result.has(pluginId)) { + throw new Error( + `Attempted to declare 'accessRestrictions' twice for plugin '${pluginId}', which is not permitted`, + ); + } + + result.set(pluginId, { + ...(permissionNames ? { permissionNames } : {}), + ...(permissionAttributes ? { permissionAttributes } : {}), + }); + } + + return result.size ? result : undefined; +} + +/** + * Reads a config value as a string or an array of strings, and deduplicates and + * splits by comma/space into a string array. Can also validate against a known + * set of values. Returns undefined if the key didn't exist or if the array + * would have ended up being empty. + */ +function stringOrStringArray( + root: Config, + key: string, + validValues?: readonly T[], +): T[] | undefined { + if (!root.has(key)) { + return undefined; + } + + const rawValues = Array.isArray(root.get(key)) + ? root.getStringArray(key) + : [root.getString(key)]; + + const values = [ + ...new Set( + rawValues + .map(v => v.split(/[ ,]/)) + .flat() + .filter(Boolean), + ), + ]; + + if (!values.length) { + return undefined; + } + + if (validValues?.length) { + for (const value of values) { + if (!validValues.includes(value as T)) { + const valid = validValues.map(k => `'${k}'`).join(', '); + throw new Error( + `Invalid value '${value}' at '${key}' in 'permissionAttributes' config, valid values are ${valid}`, + ); + } + } + } + + return values as T[]; +} + +function readPermissionNames(externalAccessEntryConfig: Config) { + return stringOrStringArray(externalAccessEntryConfig, 'permission'); +} + +function readPermissionAttributes(externalAccessEntryConfig: Config) { + const config = externalAccessEntryConfig.getOptionalConfig( + 'permissionAttribute', + ); + if (!config) { + return undefined; + } + + const validKeys = ['action']; + for (const key of config.keys()) { + if (!validKeys.includes(key)) { + const valid = validKeys.map(k => `'${k}'`).join(', '); + throw new Error( + `Invalid key '${key}' in 'permissionAttribute' config, expected ${valid}`, + ); + } + } + + const action = stringOrStringArray(config, 'action', [ + 'create', + 'read', + 'update', + 'delete', + ]); + + const result = { + ...(action ? { action } : {}), + }; + + return Object.keys(result).length ? result : undefined; +} diff --git a/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts index 8b2469f6b7..503157acc7 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts @@ -25,17 +25,35 @@ describe('LegacyTokenHandler', () => { const key1 = randomBytes(24); const key2 = randomBytes(24); const key3 = randomBytes(24); + const accessRestrictions1 = new Map( + Object.entries({ + scaffolder: {}, + }), + ); + const accessRestrictions2 = new Map( + Object.entries({ + catalog: { permissionNames: ['catalog.entity.read'] }, + }), + ); tokenHandler.add( new ConfigReader({ - secret: key1.toString('base64'), - subject: 'key1', + options: { + secret: key1.toString('base64'), + subject: 'key1', + }, + accessRestrictions: [{ plugin: 'scaffolder' }], }), ); tokenHandler.add( new ConfigReader({ - secret: key2.toString('base64'), - subject: 'key2', + options: { + secret: key2.toString('base64'), + subject: 'key2', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], }), ); tokenHandler.addOld( @@ -54,6 +72,7 @@ describe('LegacyTokenHandler', () => { await expect(tokenHandler.verifyToken(token1)).resolves.toEqual({ subject: 'key1', + accessRestrictions: accessRestrictions1, }); const token2 = await new SignJWT({ @@ -65,6 +84,7 @@ describe('LegacyTokenHandler', () => { await expect(tokenHandler.verifyToken(token2)).resolves.toEqual({ subject: 'key2', + accessRestrictions: accessRestrictions2, }); const token3 = await new SignJWT({ @@ -147,39 +167,93 @@ describe('LegacyTokenHandler', () => { // new style add, bad secrets expect(() => - handler.add(new ConfigReader({ _missingsecret: true, subject: 'ok' })), - ).toThrow(/secret/); + handler.add( + new ConfigReader({ options: { _missingsecret: true, subject: 'ok' } }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required config value at 'options.secret' in 'mock-config'"`, + ); expect(() => - handler.add(new ConfigReader({ secret: '', subject: 'ok' })), - ).toThrow(/secret/); + handler.add(new ConfigReader({ options: { secret: '', subject: 'ok' } })), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'options.secret' in 'mock-config', got empty-string, wanted string"`, + ); expect(() => - handler.add(new ConfigReader({ secret: 'has spaces', subject: 'ok' })), - ).toThrow(/secret/); + handler.add( + new ConfigReader({ options: { secret: 'has spaces', subject: 'ok' } }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal secret, must be a valid base64 string"`, + ); expect(() => - handler.add(new ConfigReader({ secret: 'hasnewline\n', subject: 'ok' })), - ).toThrow(/secret/); + handler.add( + new ConfigReader({ + options: { secret: 'hasnewline\n', subject: 'ok' }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal secret, must be a valid base64 string"`, + ); expect(() => - handler.add(new ConfigReader({ secret: 3, subject: 'ok' })), - ).toThrow(/secret/); + handler.add(new ConfigReader({ options: { secret: 3, subject: 'ok' } })), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'options.secret' in 'mock-config', got number, wanted string"`, + ); // new style add, bad subjects expect(() => - handler.add(new ConfigReader({ secret: 'b2s=', _missingsubject: true })), - ).toThrow(/subject/); - expect(() => - handler.add(new ConfigReader({ secret: 'b2s=', subject: '' })), - ).toThrow(/subject/); - expect(() => - handler.add(new ConfigReader({ secret: 'b2s=', subject: 'has spaces' })), - ).toThrow(/subject/); + handler.add( + new ConfigReader({ + options: { secret: 'b2s=', _missingsubject: true }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required config value at 'options.subject' in 'mock-config'"`, + ); expect(() => handler.add( - new ConfigReader({ secret: 'b2s=', subject: 'hasnewline\n' }), + new ConfigReader({ options: { secret: 'b2s=', subject: '' } }), ), - ).toThrow(/subject/); + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'options.subject' in 'mock-config', got empty-string, wanted string"`, + ); expect(() => - handler.add(new ConfigReader({ secret: 'b2s=', subject: 3 })), - ).toThrow(/subject/); + handler.add( + new ConfigReader({ + options: { secret: 'b2s=', subject: 'has spaces' }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal subject, must be a set of non-space characters"`, + ); + expect(() => + handler.add( + new ConfigReader({ + options: { secret: 'b2s=', subject: 'hasnewline\n' }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal subject, must be a set of non-space characters"`, + ); + expect(() => + handler.add( + new ConfigReader({ options: { secret: 'b2s=', subject: 3 } }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'options.subject' in 'mock-config', got number, wanted string"`, + ); + + // new style add, bad access restrictions + expect(() => + handler.add( + new ConfigReader({ + options: { secret: 'b2s=', subject: 'subject' }, + accessRestrictions: [{ plugin: ['a'] }], + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].plugin' in 'mock-config', got array, wanted string"`, + ); // old style add expect(() => @@ -187,18 +261,28 @@ describe('LegacyTokenHandler', () => { ).not.toThrow(); expect(() => handler.addOld(new ConfigReader({ _missingsecret: true })), - ).toThrow(/secret/); - expect(() => handler.addOld(new ConfigReader({ secret: '' }))).toThrow( - /secret/, + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required config value at 'secret' in 'mock-config'"`, + ); + expect(() => + handler.addOld(new ConfigReader({ secret: '' })), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'secret' in 'mock-config', got empty-string, wanted string"`, ); expect(() => handler.addOld(new ConfigReader({ secret: 'has spaces' })), - ).toThrow(/secret/); + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal secret, must be a valid base64 string"`, + ); expect(() => handler.addOld(new ConfigReader({ secret: 'hasnewline\n' })), - ).toThrow(/secret/); - expect(() => handler.addOld(new ConfigReader({ secret: 3 }))).toThrow( - /secret/, + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal secret, must be a valid base64 string"`, + ); + expect(() => + handler.addOld(new ConfigReader({ secret: 3 })), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'secret' in 'mock-config', got number, wanted string"`, ); }); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts index 8448f295a6..ba56d3a213 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts @@ -16,7 +16,8 @@ import { Config } from '@backstage/config'; import { base64url, decodeJwt, decodeProtectedHeader, jwtVerify } from 'jose'; -import { TokenHandler } from './types'; +import { readAccessRestrictionsFromConfig } from './helpers'; +import { AccessRestriptionsMap, TokenHandler } from './types'; /** * Handles `type: legacy` access. @@ -24,19 +25,32 @@ import { TokenHandler } from './types'; * @internal */ export class LegacyTokenHandler implements TokenHandler { - #entries: Array<{ key: Uint8Array; subject: string }> = []; + #entries = new Array<{ + key: Uint8Array; + subject: string; + accessRestrictions?: AccessRestriptionsMap; + }>(); - add(options: Config) { - this.#doAdd(options.getString('secret'), options.getString('subject')); + add(config: Config) { + const accessRestrictions = readAccessRestrictionsFromConfig(config); + this.#doAdd( + config.getString('options.secret'), + config.getString('options.subject'), + accessRestrictions, + ); } // used only for the old backend.auth.keys array - addOld(options: Config) { + addOld(config: Config) { // This choice of subject is for compatibility reasons - this.#doAdd(options.getString('secret'), 'external:backstage-plugin'); + this.#doAdd(config.getString('secret'), 'external:backstage-plugin'); } - #doAdd(secret: string, subject: string) { + #doAdd( + secret: string, + subject: string, + accessRestrictions?: AccessRestriptionsMap, + ) { if (!secret.match(/^\S+$/)) { throw new Error('Illegal secret, must be a valid base64 string'); } @@ -52,7 +66,11 @@ export class LegacyTokenHandler implements TokenHandler { throw new Error('Illegal subject, must be a set of non-space characters'); } - this.#entries.push({ key, subject }); + this.#entries.push({ + key, + subject, + accessRestrictions, + }); } async verifyToken(token: string) { @@ -79,7 +97,10 @@ export class LegacyTokenHandler implements TokenHandler { for (const entry of this.#entries) { try { await jwtVerify(token, entry.key); - return { subject: entry.subject }; + return { + subject: entry.subject, + accessRestrictions: entry.accessRestrictions, + }; } catch (e) { if (e.code !== 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') { throw e; diff --git a/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts index 86bdf0bdee..458c3f06f3 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts @@ -20,14 +20,36 @@ import { StaticTokenHandler } from './static'; describe('StaticTokenHandler', () => { it('accepts any of the added list of tokens', async () => { const handler = new StaticTokenHandler(); - handler.add(new ConfigReader({ token: 'abcabcabc', subject: 'one' })); - handler.add(new ConfigReader({ token: 'defdefdef', subject: 'two' })); + handler.add( + new ConfigReader({ + options: { token: 'abcabcabc', subject: 'one' }, + accessRestrictions: [{ plugin: 'scaffolder' }], + }), + ); + handler.add( + new ConfigReader({ + options: { token: 'defdefdef', subject: 'two' }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }), + ); + const accessRestrictionsOne = new Map(Object.entries({ scaffolder: {} })); + const accessRestrictionsTwo = new Map( + Object.entries({ + catalog: { + permissionNames: ['catalog.entity.read'], + }, + }), + ); await expect(handler.verifyToken('abcabcabc')).resolves.toEqual({ subject: 'one', + accessRestrictions: accessRestrictionsOne, }); await expect(handler.verifyToken('defdefdef')).resolves.toEqual({ subject: 'two', + accessRestrictions: accessRestrictionsTwo, }); await expect(handler.verifyToken('ghighighi')).resolves.toBeUndefined(); }); @@ -41,71 +63,89 @@ describe('StaticTokenHandler', () => { const handler = new StaticTokenHandler(); expect(() => - handler.add(new ConfigReader({ _missingtoken: true, subject: 'ok' })), + handler.add( + new ConfigReader({ options: { _missingtoken: true, subject: 'ok' } }), + ), ).toThrowErrorMatchingInlineSnapshot( - `"Missing required config value at 'token' in 'mock-config'"`, + `"Missing required config value at 'options.token' in 'mock-config'"`, ); expect(() => - handler.add(new ConfigReader({ token: '', subject: 'ok' })), + handler.add(new ConfigReader({ options: { token: '', subject: 'ok' } })), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'token' in 'mock-config', got empty-string, wanted string"`, + `"Invalid type in config for key 'options.token' in 'mock-config', got empty-string, wanted string"`, ); expect(() => - handler.add(new ConfigReader({ token: 'has spaces', subject: 'ok' })), + handler.add( + new ConfigReader({ options: { token: 'has spaces', subject: 'ok' } }), + ), ).toThrowErrorMatchingInlineSnapshot( `"Illegal token, must be a set of non-space characters"`, ); expect(() => handler.add( new ConfigReader({ - token: 'hasnewlinebutislongenough\n', - subject: 'ok', + options: { + token: 'hasnewlinebutislongenough\n', + subject: 'ok', + }, }), ), ).toThrowErrorMatchingInlineSnapshot( `"Illegal token, must be a set of non-space characters"`, ); expect(() => - handler.add(new ConfigReader({ token: 'short', subject: 'ok' })), + handler.add( + new ConfigReader({ options: { token: 'short', subject: 'ok' } }), + ), ).toThrowErrorMatchingInlineSnapshot( `"Illegal token, must be at least 8 characters length"`, ); expect(() => - handler.add(new ConfigReader({ token: 3, subject: 'ok' })), + handler.add(new ConfigReader({ options: { token: 3, subject: 'ok' } })), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'token' in 'mock-config', got number, wanted string"`, + `"Invalid type in config for key 'options.token' in 'mock-config', got number, wanted string"`, ); expect(() => handler.add( - new ConfigReader({ token: 'validtoken', _missingsubject: true }), + new ConfigReader({ + options: { token: 'validtoken', _missingsubject: true }, + }), ), ).toThrowErrorMatchingInlineSnapshot( - `"Missing required config value at 'subject' in 'mock-config'"`, - ); - expect(() => - handler.add(new ConfigReader({ token: 'validtoken', subject: '' })), - ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'subject' in 'mock-config', got empty-string, wanted string"`, + `"Missing required config value at 'options.subject' in 'mock-config'"`, ); expect(() => handler.add( - new ConfigReader({ token: 'validtoken', subject: 'has spaces' }), + new ConfigReader({ options: { token: 'validtoken', subject: '' } }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'options.subject' in 'mock-config', got empty-string, wanted string"`, + ); + expect(() => + handler.add( + new ConfigReader({ + options: { token: 'validtoken', subject: 'has spaces' }, + }), ), ).toThrowErrorMatchingInlineSnapshot( `"Illegal subject, must be a set of non-space characters"`, ); expect(() => handler.add( - new ConfigReader({ token: 'validtoken', subject: 'hasnewline\n' }), + new ConfigReader({ + options: { token: 'validtoken', subject: 'hasnewline\n' }, + }), ), ).toThrowErrorMatchingInlineSnapshot( `"Illegal subject, must be a set of non-space characters"`, ); expect(() => - handler.add(new ConfigReader({ token: 'validtoken', subject: 3 })), + handler.add( + new ConfigReader({ options: { token: 'validtoken', subject: 3 } }), + ), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'subject' in 'mock-config', got number, wanted string"`, + `"Invalid type in config for key 'options.subject' in 'mock-config', got number, wanted string"`, ); }); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/static.ts b/packages/backend-app-api/src/services/implementations/auth/external/static.ts index bae8e05f2b..8242b87dc0 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/static.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/static.ts @@ -15,7 +15,8 @@ */ import { Config } from '@backstage/config'; -import { TokenHandler } from './types'; +import { readAccessRestrictionsFromConfig } from './helpers'; +import { AccessRestriptionsMap, TokenHandler } from './types'; const MIN_TOKEN_LENGTH = 8; @@ -25,10 +26,14 @@ const MIN_TOKEN_LENGTH = 8; * @internal */ export class StaticTokenHandler implements TokenHandler { - #entries: Array<{ token: string; subject: string }> = []; + #entries = new Array<{ + token: string; + subject: string; + accessRestrictions?: AccessRestriptionsMap; + }>(); - add(options: Config) { - const token = options.getString('token'); + add(config: Config) { + const token = config.getString('options.token'); if (!token.match(/^\S+$/)) { throw new Error('Illegal token, must be a set of non-space characters'); } @@ -38,12 +43,18 @@ export class StaticTokenHandler implements TokenHandler { ); } - const subject = options.getString('subject'); + const subject = config.getString('options.subject'); if (!subject.match(/^\S+$/)) { throw new Error('Illegal subject, must be a set of non-space characters'); } - this.#entries.push({ token, subject }); + const accessRestrictions = readAccessRestrictionsFromConfig(config); + + this.#entries.push({ + token, + subject, + accessRestrictions, + }); } async verifyToken(token: string) { @@ -52,6 +63,9 @@ export class StaticTokenHandler implements TokenHandler { return undefined; } - return { subject: entry.subject }; + return { + subject: entry.subject, + accessRestrictions: entry.accessRestrictions, + }; } } diff --git a/packages/backend-app-api/src/services/implementations/auth/external/types.ts b/packages/backend-app-api/src/services/implementations/auth/external/types.ts index 5d33f09a22..54e51c1ef6 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/types.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/types.ts @@ -14,9 +14,21 @@ * limitations under the License. */ +import { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; +export type AccessRestriptionsMap = Map< + string, // plugin ID + BackstagePrincipalAccessRestrictions +>; + export interface TokenHandler { add(options: Config): void; - verifyToken(token: string): Promise<{ subject: string } | undefined>; + verifyToken(token: string): Promise< + | { + subject: string; + accessRestrictions?: AccessRestriptionsMap; + } + | undefined + >; } diff --git a/packages/backend-app-api/src/services/implementations/auth/helpers.ts b/packages/backend-app-api/src/services/implementations/auth/helpers.ts index 01fb9537a1..eebe45eb76 100644 --- a/packages/backend-app-api/src/services/implementations/auth/helpers.ts +++ b/packages/backend-app-api/src/services/implementations/auth/helpers.ts @@ -17,6 +17,7 @@ import { BackstageCredentials, BackstageNonePrincipal, + BackstagePrincipalAccessRestrictions, BackstageServicePrincipal, BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; @@ -25,6 +26,7 @@ import { InternalBackstageCredentials } from './types'; export function createCredentialsWithServicePrincipal( sub: string, token?: string, + accessRestrictions?: BackstagePrincipalAccessRestrictions, ): InternalBackstageCredentials { return { $$type: '@backstage/BackstageCredentials', @@ -33,6 +35,7 @@ export function createCredentialsWithServicePrincipal( principal: { type: 'service', subject: sub, + accessRestrictions, }, }; } diff --git a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts index c8fa0e7bbf..ecdaa1e938 100644 --- a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts @@ -31,12 +31,14 @@ export const permissionsServiceFactory = createServiceFactory({ config: coreServices.rootConfig, discovery: coreServices.discovery, tokenManager: coreServices.tokenManager, + pluginMetadata: coreServices.pluginMetadata, }, - async factory({ auth, config, discovery, tokenManager }) { + async factory({ auth, config, discovery, tokenManager, pluginMetadata }) { return ServerPermissionClient.fromConfig(config, { auth, discovery, tokenManager, + pluginId: pluginMetadata.getId(), }); }, }); diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index c65fc5cf3e..134e4b61da 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -16,6 +16,7 @@ import { isChildPath } from '@backstage/cli-common'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; +import { PermissionAttributes } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { QueryPermissionRequest } from '@backstage/plugin-permission-common'; import { QueryPermissionResponse } from '@backstage/plugin-permission-common'; @@ -136,6 +137,14 @@ export type BackstageNonePrincipal = { type: 'none'; }; +// @public +export type BackstagePrincipalAccessRestrictions = { + permissionNames?: string[]; + permissionAttributes?: { + action?: Array['action']>; + }; +}; + // @public (undocumented) export type BackstagePrincipalTypes = { user: BackstageUserPrincipal; @@ -148,6 +157,7 @@ export type BackstagePrincipalTypes = { export type BackstageServicePrincipal = { type: 'service'; subject: string; + accessRestrictions?: BackstagePrincipalAccessRestrictions; }; // @public (undocumented) diff --git a/packages/backend-plugin-api/src/services/definitions/AuthService.ts b/packages/backend-plugin-api/src/services/definitions/AuthService.ts index 827b4122cd..000ce8bbb1 100644 --- a/packages/backend-plugin-api/src/services/definitions/AuthService.ts +++ b/packages/backend-plugin-api/src/services/definitions/AuthService.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { PermissionAttributes } from '@backstage/plugin-permission-common'; import { JsonObject } from '@backstage/types'; /** @@ -40,6 +41,54 @@ export type BackstageServicePrincipal = { // Exact format TBD, possibly 'plugin:' or 'external:' subject: string; + + /** + * The access restrictions that apply to this principal. + * + * @remarks + * + * If no access restrictions are provided the principal is assumed to have + * unlimited access, at a framework level. The permissions system and + * individual plugins may or may not still apply additional access controls on + * top of this. + */ + accessRestrictions?: BackstagePrincipalAccessRestrictions; +}; + +/** + * The access restrictions that apply to a given principal. + * + * @public + */ +export type BackstagePrincipalAccessRestrictions = { + /** + * If given, the principal is limited to only performing actions with these + * named permissions. + * + * Note that this only applies where permissions checks are enabled in the + * first place. Endpoints that are not protected by the permissions system at + * all, are not affected by this setting. + * + * This array always has at least one element, or is missing entirely. + */ + permissionNames?: string[]; + /** + * If given, the principal is limited to only performing actions whose + * permissions have these attributes. + * + * Note that this only applies where permissions checks are enabled in the + * first place. Endpoints that are not protected by the permissions system at + * all, are not affected by this setting. + * + * This object always has at least one key, or is missing entirely. + */ + permissionAttributes?: { + /** + * Match any of these action values. This array always has at least one + * element, or is missing entirely. + */ + action?: Array['action']>; + }; }; /** diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index 5739add90c..f22213a285 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -20,6 +20,7 @@ export type { BackstageCredentials, BackstageUserPrincipal, BackstageServicePrincipal, + BackstagePrincipalAccessRestrictions, BackstagePrincipalTypes, BackstageNonePrincipal, } from './AuthService'; diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 4f6eb47243..5a192cb61f 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -11,6 +11,7 @@ import { Backend } from '@backstage/backend-app-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { BackstageNonePrincipal } from '@backstage/backend-plugin-api'; +import { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; import { BackstageServicePrincipal } from '@backstage/backend-plugin-api'; import { BackstageUserInfo } from '@backstage/backend-plugin-api'; import { BackstageUserPrincipal } from '@backstage/backend-plugin-api'; @@ -68,6 +69,7 @@ export namespace mockCredentials { } export function service( subject?: string, + accessRestrictions?: BackstagePrincipalAccessRestrictions, ): BackstageCredentials; export namespace service { export function header(options?: TokenOptions): string; diff --git a/packages/backend-test-utils/src/next/services/mockCredentials.test.ts b/packages/backend-test-utils/src/next/services/mockCredentials.test.ts index ed0071d4b8..343d6d6d20 100644 --- a/packages/backend-test-utils/src/next/services/mockCredentials.test.ts +++ b/packages/backend-test-utils/src/next/services/mockCredentials.test.ts @@ -134,6 +134,16 @@ describe('mockCredentials', () => { expect(mockCredentials.service.invalidHeader()).toBe( 'Bearer mock-invalid-service-token', ); + expect( + mockCredentials.service('test', { permissionNames: ['do.it'] }), + ).toEqual({ + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'service', + subject: 'test', + accessRestrictions: { permissionNames: ['do.it'] }, + }, + }); }); it('should throw on invalid user entity refs', () => { diff --git a/packages/backend-test-utils/src/next/services/mockCredentials.ts b/packages/backend-test-utils/src/next/services/mockCredentials.ts index 16d2381c73..4214320142 100644 --- a/packages/backend-test-utils/src/next/services/mockCredentials.ts +++ b/packages/backend-test-utils/src/next/services/mockCredentials.ts @@ -17,6 +17,7 @@ import { BackstageCredentials, BackstageNonePrincipal, + BackstagePrincipalAccessRestrictions, BackstageServicePrincipal, BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; @@ -202,14 +203,19 @@ export namespace mockCredentials { /** * Creates a mocked credentials object for a service principal. * - * The default subject is 'external:test-service'. + * The default subject is 'external:test-service', and no access restrictions. */ export function service( subject: string = DEFAULT_MOCK_SERVICE_SUBJECT, + accessRestrictions?: BackstagePrincipalAccessRestrictions, ): BackstageCredentials { return { $$type: '@backstage/BackstageCredentials', - principal: { type: 'service', subject }, + principal: { + type: 'service', + subject, + ...(accessRestrictions ? { accessRestrictions } : {}), + }, }; } diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 7d2910d391..fab3bc7742 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -291,6 +291,7 @@ export class ServerPermissionClient implements PermissionsService { discovery: DiscoveryService; tokenManager: TokenManager; auth?: AuthService; + pluginId?: string; }, ): ServerPermissionClient; } diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 215f624fe9..68c675745a 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -48,7 +48,9 @@ const discovery: PluginEndpointDiscovery = { }; const testBasicPermission = createPermission({ name: 'test.permission', - attributes: {}, + attributes: { + action: 'create', + }, }); const testResourcePermission = createPermission({ @@ -362,4 +364,66 @@ describe('ServerPermissionClient', () => { }); }); }); + + describe('with access restrictions', () => { + it('short circuits the response when relevant access restrictions are present', async () => { + const client = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager: mockServices.tokenManager(), + auth: mockServices.auth(), + pluginId: 'test', + }); + + // no restrictions for the given plugin + await expect( + client.authorize([{ permission: testBasicPermission }], { + credentials: mockCredentials.service('foo', {}), + }), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + + // matching permission name + await expect( + client.authorize([{ permission: testBasicPermission }], { + credentials: mockCredentials.service('foo', { + permissionNames: [testBasicPermission.name, 'other'], + }), + }), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + + // matching attributes + await expect( + client.authorize([{ permission: testBasicPermission }], { + credentials: mockCredentials.service('foo', { + permissionAttributes: { + action: [testBasicPermission.attributes.action!, 'other' as any], + }, + }), + }), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + + // matching permission name but not attributes + await expect( + client.authorize([{ permission: testBasicPermission }], { + credentials: mockCredentials.service('foo', { + permissionNames: [testBasicPermission.name], + permissionAttributes: { + action: ['other' as any], + }, + }), + }), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + + // matching attributes but not permission name + await expect( + client.authorize([{ permission: testBasicPermission }], { + credentials: mockCredentials.service('foo', { + permissionNames: ['wrong-name'], + permissionAttributes: { + action: [testBasicPermission.attributes.action!], + }, + }), + }), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + }); + }); }); diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index f8570fee74..2c2ccefbf4 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -33,18 +33,21 @@ import { AuthorizePermissionResponse, PolicyDecision, QueryPermissionRequest, + DefinitivePolicyDecision, } from '@backstage/plugin-permission-common'; /** * A thin wrapper around - * {@link @backstage/plugin-permission-common#PermissionClient} that allows all - * service-to-service requests. + * {@link @backstage/plugin-permission-common#PermissionClient} that ensures the + * proper short-circuit handling of service principals. + * * @public */ export class ServerPermissionClient implements PermissionsService { readonly #auth: AuthService; readonly #permissionClient: PermissionClient; readonly #permissionEnabled: boolean; + readonly #pluginId?: string; static fromConfig( config: Config, @@ -52,6 +55,7 @@ export class ServerPermissionClient implements PermissionsService { discovery: DiscoveryService; tokenManager: TokenManager; auth?: AuthService; + pluginId?: string; }, ) { const { discovery, tokenManager } = options; @@ -74,6 +78,7 @@ export class ServerPermissionClient implements PermissionsService { auth, permissionClient, permissionEnabled, + pluginId: options.pluginId, }); } @@ -81,16 +86,26 @@ export class ServerPermissionClient implements PermissionsService { auth: AuthService; permissionClient: PermissionClient; permissionEnabled: boolean; + pluginId?: string; }) { this.#auth = options.auth; this.#permissionClient = options.permissionClient; this.#permissionEnabled = options.permissionEnabled; + this.#pluginId = options.pluginId; } async authorizeConditional( queries: QueryPermissionRequest[], options?: PermissionsServiceRequestOptions, ): Promise { + const maybeResponse = this.#decideBasedOnPrincipalAccessRestrictions( + queries, + options, + ); + if (maybeResponse) { + return maybeResponse; + } + if (await this.#shouldPermissionsBeApplied(options)) { return this.#permissionClient.authorizeConditional( queries, @@ -105,6 +120,14 @@ export class ServerPermissionClient implements PermissionsService { requests: AuthorizePermissionRequest[], options?: PermissionsServiceRequestOptions, ): Promise { + const maybeResponse = this.#decideBasedOnPrincipalAccessRestrictions( + requests, + options, + ); + if (maybeResponse) { + return maybeResponse; + } + if (await this.#shouldPermissionsBeApplied(options)) { return this.#permissionClient.authorize( requests, @@ -130,6 +153,44 @@ export class ServerPermissionClient implements PermissionsService { return options; } + #decideBasedOnPrincipalAccessRestrictions( + requests: Array, + options?: PermissionsServiceRequestOptions, + ): DefinitivePolicyDecision[] | undefined { + if (!options || !('credentials' in options)) { + return undefined; + } + + // Bail out to the old behavior if + // - the principal is not a service + // - the principal was apparently unrestricted + // - we are in legacy mode because nobody passed in a plugin ID + const credentials = options.credentials; + if ( + !this.#auth.isPrincipal(credentials, 'service') || + !credentials.principal.accessRestrictions || + !this.#pluginId + ) { + return undefined; + } + + const { permissionNames, permissionAttributes } = + credentials.principal.accessRestrictions; + + return requests.map(query => { + if (permissionNames && !permissionNames.includes(query.permission.name)) { + return { result: AuthorizeResult.DENY }; + } + if (permissionAttributes?.action) { + const action = query.permission.attributes?.action; + if (!action || !permissionAttributes.action.includes(action)) { + return { result: AuthorizeResult.DENY }; + } + } + return { result: AuthorizeResult.ALLOW }; + }); + } + async #shouldPermissionsBeApplied( options?: PermissionsServiceRequestOptions, ) { From b155d854bc01685522bdbdff01f57bf19413dc75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 19 May 2024 22:50:13 +0200 Subject: [PATCH 067/118] skip the plugin id for permissions client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../permissions/permissionsServiceFactory.ts | 4 +--- plugins/permission-node/api-report.md | 1 - .../permission-node/src/ServerPermissionClient.test.ts | 1 - plugins/permission-node/src/ServerPermissionClient.ts | 9 +-------- 4 files changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts index ecdaa1e938..c8fa0e7bbf 100644 --- a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts @@ -31,14 +31,12 @@ export const permissionsServiceFactory = createServiceFactory({ config: coreServices.rootConfig, discovery: coreServices.discovery, tokenManager: coreServices.tokenManager, - pluginMetadata: coreServices.pluginMetadata, }, - async factory({ auth, config, discovery, tokenManager, pluginMetadata }) { + async factory({ auth, config, discovery, tokenManager }) { return ServerPermissionClient.fromConfig(config, { auth, discovery, tokenManager, - pluginId: pluginMetadata.getId(), }); }, }); diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index fab3bc7742..7d2910d391 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -291,7 +291,6 @@ export class ServerPermissionClient implements PermissionsService { discovery: DiscoveryService; tokenManager: TokenManager; auth?: AuthService; - pluginId?: string; }, ): ServerPermissionClient; } diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 68c675745a..9becad4d7b 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -371,7 +371,6 @@ describe('ServerPermissionClient', () => { discovery, tokenManager: mockServices.tokenManager(), auth: mockServices.auth(), - pluginId: 'test', }); // no restrictions for the given plugin diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index 2c2ccefbf4..6e3901d65b 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -47,7 +47,6 @@ export class ServerPermissionClient implements PermissionsService { readonly #auth: AuthService; readonly #permissionClient: PermissionClient; readonly #permissionEnabled: boolean; - readonly #pluginId?: string; static fromConfig( config: Config, @@ -55,7 +54,6 @@ export class ServerPermissionClient implements PermissionsService { discovery: DiscoveryService; tokenManager: TokenManager; auth?: AuthService; - pluginId?: string; }, ) { const { discovery, tokenManager } = options; @@ -78,7 +76,6 @@ export class ServerPermissionClient implements PermissionsService { auth, permissionClient, permissionEnabled, - pluginId: options.pluginId, }); } @@ -86,12 +83,10 @@ export class ServerPermissionClient implements PermissionsService { auth: AuthService; permissionClient: PermissionClient; permissionEnabled: boolean; - pluginId?: string; }) { this.#auth = options.auth; this.#permissionClient = options.permissionClient; this.#permissionEnabled = options.permissionEnabled; - this.#pluginId = options.pluginId; } async authorizeConditional( @@ -164,12 +159,10 @@ export class ServerPermissionClient implements PermissionsService { // Bail out to the old behavior if // - the principal is not a service // - the principal was apparently unrestricted - // - we are in legacy mode because nobody passed in a plugin ID const credentials = options.credentials; if ( !this.#auth.isPrincipal(credentials, 'service') || - !credentials.principal.accessRestrictions || - !this.#pluginId + !credentials.principal.accessRestrictions ) { return undefined; } From 17be3e69621929134fa80fde8c2389e865782f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 20 May 2024 11:27:46 +0200 Subject: [PATCH 068/118] one token manager per plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../auth/DefaultAuthService.ts | 18 +----- .../auth/authServiceFactory.ts | 19 ++----- .../external/ExternalTokenHandler.test.ts | 55 +++++++++++++++++++ .../auth/external/ExternalTokenHandler.ts | 43 ++++++++++++--- .../auth/external/legacy.test.ts | 4 +- .../implementations/auth/external/legacy.ts | 33 ++++++----- .../auth/external/static.test.ts | 4 +- .../implementations/auth/external/static.ts | 45 ++++++--------- .../implementations/auth/external/types.ts | 2 +- 9 files changed, 138 insertions(+), 85 deletions(-) create mode 100644 packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts diff --git a/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts b/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts index d0c11a4eee..b4feb46cce 100644 --- a/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts +++ b/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts @@ -23,11 +23,7 @@ import { BackstageServicePrincipal, BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; -import { - AuthenticationError, - ForwardedError, - NotAllowedError, -} from '@backstage/errors'; +import { AuthenticationError, ForwardedError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import { decodeJwt } from 'jose'; import { ExternalTokenHandler } from './external/ExternalTokenHandler'; @@ -86,20 +82,10 @@ export class DefaultAuthService implements AuthService { const externalResult = await this.externalTokenHandler.verifyToken(token); if (externalResult) { - const restrictions = externalResult.accessRestrictions; - if (restrictions) { - if (!restrictions.has(this.pluginId)) { - const valid = [...restrictions.keys()].map(k => `'${k}'`).join(', '); - throw new NotAllowedError( - `This token's access is restricted to plugin(s) ${valid}`, - ); - } - } - return createCredentialsWithServicePrincipal( externalResult.subject, undefined, - restrictions?.get(this.pluginId), + externalResult.accessRestrictions, ); } diff --git a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts index 7bbd012c01..ad4fb17d88 100644 --- a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts @@ -39,19 +39,7 @@ export const authServiceFactory = createServiceFactory({ // new auth services in the new backend system. tokenManager: coreServices.tokenManager, }, - async createRootContext({ config, logger }) { - const externalTokens = ExternalTokenHandler.create({ - config, - logger, - }); - return { - externalTokens, - }; - }, - async factory( - { config, discovery, plugin, tokenManager, logger, database }, - { externalTokens }, - ) { + async factory({ config, discovery, plugin, tokenManager, logger, database }) { const disableDefaultAuthPolicy = Boolean( config.getOptionalBoolean( 'backend.auth.dangerouslyDisableDefaultAuthPolicy', @@ -73,6 +61,11 @@ export const authServiceFactory = createServiceFactory({ publicKeyStore, discovery, }); + const externalTokens = ExternalTokenHandler.create({ + ownPluginId: plugin.getId(), + config, + logger, + }); return new DefaultAuthService( userTokens, diff --git a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts new file mode 100644 index 0000000000..1e82f76134 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts @@ -0,0 +1,55 @@ +/* + * 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. + */ + +import { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; +import { ExternalTokenHandler } from './ExternalTokenHandler'; +import { TokenHandler } from './types'; + +describe('ExternalTokenHandler', () => { + it('skips over inner handlers that do not match, and applies plugin restrictions', async () => { + const handler1: TokenHandler = { + add: jest.fn(), + verifyToken: jest.fn().mockResolvedValue(undefined), + }; + + const handler2: TokenHandler = { + add: jest.fn(), + verifyToken: jest.fn().mockResolvedValue({ + subject: 'sub', + allAccessRestrictions: new Map( + Object.entries({ + plugin1: { + permissionNames: ['do.it'], + } satisfies BackstagePrincipalAccessRestrictions, + }), + ), + }), + }; + + const plugin1 = new ExternalTokenHandler('plugin1', [handler1, handler2]); + const plugin2 = new ExternalTokenHandler('plugin2', [handler1, handler2]); + + await expect(plugin1.verifyToken('token')).resolves.toEqual({ + subject: 'sub', + accessRestrictions: { permissionNames: ['do.it'] }, + }); + await expect( + plugin2.verifyToken('token'), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"This token's access is restricted to plugin(s) 'plugin1'"`, + ); + }); +}); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts index 82603953d0..72eeae6fcf 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts @@ -15,16 +15,19 @@ */ import { + BackstagePrincipalAccessRestrictions, LoggerService, RootConfigService, } from '@backstage/backend-plugin-api'; +import { NotAllowedError } from '@backstage/errors'; import { LegacyTokenHandler } from './legacy'; import { StaticTokenHandler } from './static'; import { JWKSHandler } from './jwks'; -import { AccessRestriptionsMap, TokenHandler } from './types'; +import { TokenHandler } from './types'; const NEW_CONFIG_KEY = 'backend.auth.externalAccess'; const OLD_CONFIG_KEY = 'backend.auth.keys'; +let loggedDeprecationWarning = false; /** * Handles all types of external caller token types (i.e. not Backstage user @@ -34,10 +37,11 @@ const OLD_CONFIG_KEY = 'backend.auth.keys'; */ export class ExternalTokenHandler { static create(options: { + ownPluginId: string; config: RootConfigService; logger: LoggerService; }): ExternalTokenHandler { - const { config, logger } = options; + const { ownPluginId, config, logger } = options; const staticHandler = new StaticTokenHandler(); const legacyHandler = new LegacyTokenHandler(); @@ -66,7 +70,8 @@ export class ExternalTokenHandler { // Load the old keys too const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? []; - if (legacyConfigs.length) { + if (legacyConfigs.length && !loggedDeprecationWarning) { + loggedDeprecationWarning = true; logger.warn( `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`, ); @@ -75,24 +80,48 @@ export class ExternalTokenHandler { legacyHandler.addOld(handlerConfig); } - return new ExternalTokenHandler(Object.values(handlers)); + return new ExternalTokenHandler(ownPluginId, Object.values(handlers)); } - constructor(private readonly handlers: TokenHandler[]) {} + constructor( + private readonly ownPluginId: string, + private readonly handlers: TokenHandler[], + ) {} async verifyToken(token: string): Promise< | { subject: string; - accessRestrictions?: AccessRestriptionsMap; + accessRestrictions?: BackstagePrincipalAccessRestrictions; } | undefined > { for (const handler of this.handlers) { const result = await handler.verifyToken(token); if (result) { - return result; + const { allAccessRestrictions, ...rest } = result; + if (allAccessRestrictions) { + const accessRestrictions = allAccessRestrictions.get( + this.ownPluginId, + ); + if (!accessRestrictions) { + const valid = [...allAccessRestrictions.keys()] + .map(k => `'${k}'`) + .join(', '); + throw new NotAllowedError( + `This token's access is restricted to plugin(s) ${valid}`, + ); + } + + return { + ...rest, + accessRestrictions, + }; + } + + return rest; } } + return undefined; } } diff --git a/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts index 503157acc7..c674e46ec9 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts @@ -72,7 +72,7 @@ describe('LegacyTokenHandler', () => { await expect(tokenHandler.verifyToken(token1)).resolves.toEqual({ subject: 'key1', - accessRestrictions: accessRestrictions1, + allAccessRestrictions: accessRestrictions1, }); const token2 = await new SignJWT({ @@ -84,7 +84,7 @@ describe('LegacyTokenHandler', () => { await expect(tokenHandler.verifyToken(token2)).resolves.toEqual({ subject: 'key2', - accessRestrictions: accessRestrictions2, + allAccessRestrictions: accessRestrictions2, }); const token3 = await new SignJWT({ diff --git a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts index ba56d3a213..4eb982314b 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts @@ -27,16 +27,18 @@ import { AccessRestriptionsMap, TokenHandler } from './types'; export class LegacyTokenHandler implements TokenHandler { #entries = new Array<{ key: Uint8Array; - subject: string; - accessRestrictions?: AccessRestriptionsMap; + result: { + subject: string; + allAccessRestrictions?: AccessRestriptionsMap; + }; }>(); add(config: Config) { - const accessRestrictions = readAccessRestrictionsFromConfig(config); + const allAccessRestrictions = readAccessRestrictionsFromConfig(config); this.#doAdd( config.getString('options.secret'), config.getString('options.subject'), - accessRestrictions, + allAccessRestrictions, ); } @@ -49,10 +51,12 @@ export class LegacyTokenHandler implements TokenHandler { #doAdd( secret: string, subject: string, - accessRestrictions?: AccessRestriptionsMap, + allAccessRestrictions?: AccessRestriptionsMap, ) { if (!secret.match(/^\S+$/)) { throw new Error('Illegal secret, must be a valid base64 string'); + } else if (!subject.match(/^\S+$/)) { + throw new Error('Illegal subject, must be a set of non-space characters'); } let key: Uint8Array; @@ -62,14 +66,12 @@ export class LegacyTokenHandler implements TokenHandler { throw new Error('Illegal secret, must be a valid base64 string'); } - if (!subject.match(/^\S+$/)) { - throw new Error('Illegal subject, must be a set of non-space characters'); - } - this.#entries.push({ key, - subject, - accessRestrictions, + result: { + subject, + allAccessRestrictions, + }, }); } @@ -94,13 +96,10 @@ export class LegacyTokenHandler implements TokenHandler { return undefined; } - for (const entry of this.#entries) { + for (const { key, result } of this.#entries) { try { - await jwtVerify(token, entry.key); - return { - subject: entry.subject, - accessRestrictions: entry.accessRestrictions, - }; + await jwtVerify(token, key); + return result; } catch (e) { if (e.code !== 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') { throw e; diff --git a/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts index 458c3f06f3..a5945daba9 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts @@ -45,11 +45,11 @@ describe('StaticTokenHandler', () => { await expect(handler.verifyToken('abcabcabc')).resolves.toEqual({ subject: 'one', - accessRestrictions: accessRestrictionsOne, + allAccessRestrictions: accessRestrictionsOne, }); await expect(handler.verifyToken('defdefdef')).resolves.toEqual({ subject: 'two', - accessRestrictions: accessRestrictionsTwo, + allAccessRestrictions: accessRestrictionsTwo, }); await expect(handler.verifyToken('ghighighi')).resolves.toBeUndefined(); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/static.ts b/packages/backend-app-api/src/services/implementations/auth/external/static.ts index 8242b87dc0..1e89d8c6a1 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/static.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/static.ts @@ -26,46 +26,37 @@ const MIN_TOKEN_LENGTH = 8; * @internal */ export class StaticTokenHandler implements TokenHandler { - #entries = new Array<{ - token: string; - subject: string; - accessRestrictions?: AccessRestriptionsMap; - }>(); + #entries = new Map< + string, + { + subject: string; + allAccessRestrictions?: AccessRestriptionsMap; + } + >(); add(config: Config) { const token = config.getString('options.token'); + const subject = config.getString('options.subject'); + const allAccessRestrictions = readAccessRestrictionsFromConfig(config); + if (!token.match(/^\S+$/)) { throw new Error('Illegal token, must be a set of non-space characters'); - } - if (token.length < MIN_TOKEN_LENGTH) { + } else if (token.length < MIN_TOKEN_LENGTH) { throw new Error( `Illegal token, must be at least ${MIN_TOKEN_LENGTH} characters length`, ); - } - - const subject = config.getString('options.subject'); - if (!subject.match(/^\S+$/)) { + } else if (!subject.match(/^\S+$/)) { throw new Error('Illegal subject, must be a set of non-space characters'); + } else if (this.#entries.has(token)) { + throw new Error( + 'Static externalAccess token was declared more than once', + ); } - const accessRestrictions = readAccessRestrictionsFromConfig(config); - - this.#entries.push({ - token, - subject, - accessRestrictions, - }); + this.#entries.set(token, { subject, allAccessRestrictions }); } async verifyToken(token: string) { - const entry = this.#entries.find(e => e.token === token); - if (!entry) { - return undefined; - } - - return { - subject: entry.subject, - accessRestrictions: entry.accessRestrictions, - }; + return this.#entries.get(token); } } diff --git a/packages/backend-app-api/src/services/implementations/auth/external/types.ts b/packages/backend-app-api/src/services/implementations/auth/external/types.ts index 54e51c1ef6..6a1fd084ed 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/types.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/types.ts @@ -27,7 +27,7 @@ export interface TokenHandler { verifyToken(token: string): Promise< | { subject: string; - accessRestrictions?: AccessRestriptionsMap; + allAccessRestrictions?: AccessRestriptionsMap; } | undefined >; From 0639b07aa1a0df026b35b24f07e00dc1cfd72da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 20 May 2024 15:47:49 +0200 Subject: [PATCH 069/118] refactor the client a bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../implementations/auth/external/legacy.ts | 6 + .../src/ServerPermissionClient.test.ts | 283 ++++++++++++++---- .../src/ServerPermissionClient.ts | 135 ++++----- 3 files changed, 292 insertions(+), 132 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts index 4eb982314b..9c60e70707 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts @@ -66,6 +66,12 @@ export class LegacyTokenHandler implements TokenHandler { throw new Error('Illegal secret, must be a valid base64 string'); } + if (this.#entries.some(e => e.key === key)) { + throw new Error( + 'Legacy externalAccess token was declared more than once', + ); + } + this.#entries.push({ key, result: { diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 9becad4d7b..8273606845 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -366,63 +366,242 @@ describe('ServerPermissionClient', () => { }); describe('with access restrictions', () => { - it('short circuits the response when relevant access restrictions are present', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager(), - auth: mockServices.auth(), - }); - - // no restrictions for the given plugin - await expect( - client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service('foo', {}), - }), - ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); - - // matching permission name - await expect( - client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service('foo', { - permissionNames: [testBasicPermission.name, 'other'], + it.each([{ enabled: true }, { enabled: false }])( + 'short circuits the response when using a service principal, applying the relevant access restrictions if present, when permissions %p', + async permissionConfig => { + const client = ServerPermissionClient.fromConfig( + new ConfigReader({ + permission: permissionConfig, }), - }), - ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + { + discovery, + tokenManager: mockServices.tokenManager(), + auth: mockServices.auth(), + }, + ); - // matching attributes - await expect( - client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service('foo', { - permissionAttributes: { - action: [testBasicPermission.attributes.action!, 'other' as any], + // no restrictions for the given plugin + await expect( + client.authorize( + [ + { + permission: createPermission({ + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', {}), }, - }), - }), - ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); - - // matching permission name but not attributes - await expect( - client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service('foo', { - permissionNames: [testBasicPermission.name], - permissionAttributes: { - action: ['other' as any], + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + await expect( + client.authorizeConditional( + [ + { + resourceRef: undefined as any, + permission: createPermission({ + resourceType: 'test', + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', {}), }, - }), - }), - ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); - // matching attributes but not permission name - await expect( - client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service('foo', { - permissionNames: ['wrong-name'], - permissionAttributes: { - action: [testBasicPermission.attributes.action!], + // matching permission name + await expect( + client.authorize( + [ + { + permission: createPermission({ + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['test.permission', 'other'], + }), }, - }), - }), - ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); - }); + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + await expect( + client.authorizeConditional( + [ + { + resourceRef: undefined as any, + permission: createPermission({ + resourceType: 'test', + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['test.permission', 'other'], + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + + // matching attributes + await expect( + client.authorize( + [ + { + permission: createPermission({ + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionAttributes: { + action: ['create', 'other' as any], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + await expect( + client.authorizeConditional( + [ + { + resourceRef: undefined as any, + permission: createPermission({ + resourceType: 'test', + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionAttributes: { + action: ['create', 'other' as any], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + + // matching permission name but not attributes + await expect( + client.authorize( + [ + { + permission: createPermission({ + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['test.permission'], + permissionAttributes: { + action: ['other' as any], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + await expect( + client.authorizeConditional( + [ + { + resourceRef: undefined as any, + permission: createPermission({ + resourceType: 'test', + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['test.permission'], + permissionAttributes: { + action: ['other' as any], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + + // matching attributes but not permission name + await expect( + client.authorize( + [ + { + permission: createPermission({ + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['wrong-name'], + permissionAttributes: { + action: ['create'], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + await expect( + client.authorizeConditional( + [ + { + resourceRef: undefined as any, + permission: createPermission({ + resourceType: 'test', + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['wrong-name'], + permissionAttributes: { + action: ['create'], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + }, + ); }); }); diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index 6e3901d65b..cb39f310bd 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -21,26 +21,27 @@ import { import { AuthService, BackstageCredentials, + BackstageServicePrincipal, DiscoveryService, PermissionsService, PermissionsServiceRequestOptions, } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { - AuthorizeResult, - PermissionClient, AuthorizePermissionRequest, AuthorizePermissionResponse, + AuthorizeResult, + DefinitivePolicyDecision, + Permission, + PermissionClient, PolicyDecision, QueryPermissionRequest, - DefinitivePolicyDecision, } from '@backstage/plugin-permission-common'; /** * A thin wrapper around - * {@link @backstage/plugin-permission-common#PermissionClient} that ensures the - * proper short-circuit handling of service principals. - * + * {@link @backstage/plugin-permission-common#PermissionClient} that allows all + * service-to-service requests. * @public */ export class ServerPermissionClient implements PermissionsService { @@ -93,47 +94,39 @@ export class ServerPermissionClient implements PermissionsService { queries: QueryPermissionRequest[], options?: PermissionsServiceRequestOptions, ): Promise { - const maybeResponse = this.#decideBasedOnPrincipalAccessRestrictions( + const credentials = await this.#getIncomingCredentials(options); + if (credentials && this.#auth.isPrincipal(credentials, 'service')) { + return this.#servicePrincipalDecision(queries, credentials); + } else if (!this.#permissionEnabled) { + return queries.map(_ => ({ result: AuthorizeResult.ALLOW })); + } + + return this.#permissionClient.authorizeConditional( queries, - options, + await this.#getRequestOptions(options), ); - if (maybeResponse) { - return maybeResponse; - } - - if (await this.#shouldPermissionsBeApplied(options)) { - return this.#permissionClient.authorizeConditional( - queries, - await this.#getRequestOptions(options), - ); - } - - return queries.map(_ => ({ result: AuthorizeResult.ALLOW })); } async authorize( requests: AuthorizePermissionRequest[], options?: PermissionsServiceRequestOptions, ): Promise { - const maybeResponse = this.#decideBasedOnPrincipalAccessRestrictions( + const credentials = await this.#getIncomingCredentials(options); + if (credentials && this.#auth.isPrincipal(credentials, 'service')) { + return this.#servicePrincipalDecision(requests, credentials); + } else if (!this.#permissionEnabled) { + return requests.map(_ => ({ result: AuthorizeResult.ALLOW })); + } + + return this.#permissionClient.authorize( requests, - options, + await this.#getRequestOptions(options), ); - if (maybeResponse) { - return maybeResponse; - } - - if (await this.#shouldPermissionsBeApplied(options)) { - return this.#permissionClient.authorize( - requests, - await this.#getRequestOptions(options), - ); - } - - return requests.map(_ => ({ result: AuthorizeResult.ALLOW })); } - async #getRequestOptions(options?: PermissionsServiceRequestOptions) { + async #getRequestOptions( + options?: PermissionsServiceRequestOptions, + ): Promise<{ token?: string } | undefined> { if (options && 'credentials' in options) { if (this.#auth.isPrincipal(options.credentials, 'none')) { return {}; @@ -148,66 +141,48 @@ export class ServerPermissionClient implements PermissionsService { return options; } - #decideBasedOnPrincipalAccessRestrictions( - requests: Array, + async #getIncomingCredentials( options?: PermissionsServiceRequestOptions, - ): DefinitivePolicyDecision[] | undefined { - if (!options || !('credentials' in options)) { - return undefined; + ): Promise { + if (options && 'credentials' in options) { + return options.credentials; } - // Bail out to the old behavior if - // - the principal is not a service - // - the principal was apparently unrestricted - const credentials = options.credentials; - if ( - !this.#auth.isPrincipal(credentials, 'service') || - !credentials.principal.accessRestrictions - ) { - return undefined; + if (options?.token) { + try { + return await this.#auth.authenticate(options.token); + } catch { + // ignore + } } + return undefined; + } + + /** + * For service principals, we can always make an immediate definitive decision + * based on their associated access restrictions (if any). + */ + #servicePrincipalDecision( + input: { permission: Permission }[], + credentials: BackstageCredentials, + ): DefinitivePolicyDecision[] { const { permissionNames, permissionAttributes } = - credentials.principal.accessRestrictions; + credentials.principal.accessRestrictions ?? {}; - return requests.map(query => { - if (permissionNames && !permissionNames.includes(query.permission.name)) { + return input.map(item => { + if (permissionNames && !permissionNames.includes(item.permission.name)) { return { result: AuthorizeResult.DENY }; } + if (permissionAttributes?.action) { - const action = query.permission.attributes?.action; + const action = item.permission.attributes?.action; if (!action || !permissionAttributes.action.includes(action)) { return { result: AuthorizeResult.DENY }; } } + return { result: AuthorizeResult.ALLOW }; }); } - - async #shouldPermissionsBeApplied( - options?: PermissionsServiceRequestOptions, - ) { - if (!this.#permissionEnabled) { - return false; - } - - let credentials: BackstageCredentials; - if (options && 'credentials' in options) { - credentials = options.credentials; - } else { - if (!options?.token) { - return true; - } - try { - credentials = await this.#auth.authenticate(options.token); - } catch { - return true; - } - } - - if (this.#auth.isPrincipal(credentials, 'service')) { - return false; - } - return true; - } } From c2ea75f4733e6f9bb3d9c8754aad405c3b4a3cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 22 May 2024 16:40:53 +0200 Subject: [PATCH 070/118] update the jwks external auth to use singular nouns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/auth/service-to-service-auth.md | 18 ++--- packages/backend-app-api/config.d.ts | 20 +++--- .../auth/external/helpers.test.ts | 68 ++++++++++++++++++- .../implementations/auth/external/helpers.ts | 11 ++- .../auth/external/jwks.test.ts | 34 +++++----- .../implementations/auth/external/jwks.ts | 7 +- 6 files changed, 112 insertions(+), 46 deletions(-) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index a728bf064b..ee88e45305 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -96,29 +96,25 @@ backend: - type: jwks options: url: https://example.com/.well-known/jwks.json - issuers: - - https://example.com - algorithms: - - RS256 - audiences: - - example + issuer: https://example.com + algorithm: RS256 + audience: example, other-example subjectPrefix: custom-prefix - type: jwks options: url: https://another-example.com/.well-known/jwks.json - issuers: - - https://example.com + issuer: https://example.com ``` The URL should point at an unauthenticated endpoint that returns the JWKS. -Issuers specifies the issuer(s) of the JWT that the authenticating app will accept. +`issuer` specifies the issuer(s) of the JWT that the authenticating app will accept. Passed JWTs must have an `iss` claim which matches one of the specified issuers. -Algorithms specifies the algorithm(s) that are used to verify the JWT. The passed JWTs +`algorithm` specifies the algorithm(s) that are used to verify the JWT. The passed JWTs must have been signed using one of the listed algorithms. -Audiences specify the intended audience(s) of the JWT. The passed JWTs must have an "aud" +`audience` specifies the intended audience(s) of the JWT. The passed JWTs must have an "aud" claim that matches one of the audiences specified, or have no audience specified. For additional details regarding the JWKS configuration, please consult your authentication diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index 5b72fef54c..80f97cb4b5 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -226,30 +226,30 @@ export interface Config { type: 'jwks'; options: { /** - * Sets the algorithms that should be used to verify the JWT tokens. + * The full URL of the JWKS endpoint. + */ + url: string; + /** + * Sets the algorithm(s) that should be used to verify the JWT tokens. * The passed JWTs must have been signed using one of the listed algorithms. */ - algorithms?: string[]; + algorithm?: string | string[]; /** - * Sets the issuers that should be used to verify the JWT tokens. + * Sets the issuer(s) that should be used to verify the JWT tokens. * Passed JWTs must have an `iss` claim which matches one of the specified issuers. */ - issuers?: string[]; + issuer?: string | string[]; /** - * Sets the audiences that should be used to verify the JWT tokens. + * Sets the audience(s) that should be used to verify the JWT tokens. * The passed JWTs must have an "aud" claim that matches one of the audiences specified, * or have no audience specified. */ - audiences?: string[]; + audience?: string | string[]; /** * Sets an optional subject prefix. Passes the subject to called plugins. * Useful for debugging and tracking purposes. */ subjectPrefix?: string; - /** - * Sets the URL containing the JWKS endpoint. - */ - url: string; }; } >; diff --git a/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts index ee0dc3cb87..5b8e40ed4f 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts @@ -15,8 +15,74 @@ */ import { ConfigReader } from '@backstage/config'; -import { readAccessRestrictionsFromConfig } from './helpers'; +import { + readAccessRestrictionsFromConfig, + readStringOrStringArrayFromConfig, +} from './helpers'; import { JsonObject } from '@backstage/types'; +import { mockServices } from '@backstage/backend-test-utils'; + +describe('readStringOrStringArrayFromConfig', () => { + it('handles all cases correctly', () => { + const config = mockServices.rootConfig({ + data: { + wrongType: 1, + wrongTypeInArray: [1], + singleString: 'a', + spaceSeparatedString: 'a b c', + commaSeparatedString: 'a,b,c', + mixedSeparatorsString: 'a b,c ,, d', + emptyString: '', + emptyArray: [], + simpleArray: ['a', 'b', 'c'], + arrayWithSeparators: ['a b', 'c,d', 'e'], + complexDuplicates: ['a', 'a b', 'a', 'b, a'], + }, + }); + + expect(() => + readStringOrStringArrayFromConfig(config, 'wrongType'), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'wrongType' in 'mock-config', got number, wanted string"`, + ); + expect(() => + readStringOrStringArrayFromConfig(config, 'wrongTypeInArray'), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'wrongTypeInArray[0]' in 'mock-config', got number, wanted string-array"`, + ); + expect(readStringOrStringArrayFromConfig(config, 'singleString')).toEqual([ + 'a', + ]); + expect( + readStringOrStringArrayFromConfig(config, 'spaceSeparatedString'), + ).toEqual(['a', 'b', 'c']); + expect( + readStringOrStringArrayFromConfig(config, 'commaSeparatedString'), + ).toEqual(['a', 'b', 'c']); + expect( + readStringOrStringArrayFromConfig(config, 'mixedSeparatorsString'), + ).toEqual(['a', 'b', 'c', 'd']); + expect(() => + readStringOrStringArrayFromConfig(config, 'emptyString'), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'emptyString' in 'mock-config', got empty-string, wanted string"`, + ); + expect( + readStringOrStringArrayFromConfig(config, 'emptyArray'), + ).toBeUndefined(); + expect(readStringOrStringArrayFromConfig(config, 'simpleArray')).toEqual([ + 'a', + 'b', + 'c', + ]); + expect( + readStringOrStringArrayFromConfig(config, 'arrayWithSeparators'), + ).toEqual(['a', 'b', 'c', 'd', 'e']); + expect( + readStringOrStringArrayFromConfig(config, 'complexDuplicates'), + ).toEqual(['a', 'b']); + }); +}); describe('readAccessRestrictionsFromConfig', () => { function r(config: JsonObject) { diff --git a/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts b/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts index 5d199a5067..d6aa0a01ff 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts @@ -66,8 +66,10 @@ export function readAccessRestrictionsFromConfig( * splits by comma/space into a string array. Can also validate against a known * set of values. Returns undefined if the key didn't exist or if the array * would have ended up being empty. + * + * @internal */ -function stringOrStringArray( +export function readStringOrStringArrayFromConfig( root: Config, key: string, validValues?: readonly T[], @@ -108,7 +110,10 @@ function stringOrStringArray( } function readPermissionNames(externalAccessEntryConfig: Config) { - return stringOrStringArray(externalAccessEntryConfig, 'permission'); + return readStringOrStringArrayFromConfig( + externalAccessEntryConfig, + 'permission', + ); } function readPermissionAttributes(externalAccessEntryConfig: Config) { @@ -129,7 +134,7 @@ function readPermissionAttributes(externalAccessEntryConfig: Config) { } } - const action = stringOrStringArray(config, 'action', [ + const action = readStringOrStringArrayFromConfig(config, 'action', [ 'create', 'read', 'update', diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts index 4cbdcb1cb0..0466cdf034 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts @@ -101,9 +101,9 @@ describe('JWKSHandler', () => { it('verifies token with valid entry', async () => { const validEntry = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['RS256'], - issuers: [mockBaseUrl], - audiences: ['backstage'], + algorithm: 'RS256', + issuer: mockBaseUrl, + audience: 'backstage', }; const jwksHandler = new JWKSHandler(); @@ -121,16 +121,16 @@ describe('JWKSHandler', () => { it('skips invalid entry and continues verification', async () => { const invalidEntry = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['RS256'], - issuers: ['fakeIssuer'], - audiences: ['fakeAud'], + algorithm: 'RS256', + issuer: ['fakeIssuer'], + audience: ['fakeAud'], }; const validEntry = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['RS256'], - issuers: ['multiple-issuers', mockBaseUrl], - audiences: ['multiple-audiences', 'backstage'], + algorithm: 'RS256', + issuer: ['multiple-issuers', mockBaseUrl], + audience: ['multiple-audiences', 'backstage'], }; const jwksHandler = new JWKSHandler(); @@ -149,16 +149,14 @@ describe('JWKSHandler', () => { it('returns undefined if no valid entry found', async () => { const invalidEntry1 = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['RS256'], - issuers: [mockBaseUrl], - audiences: [], + algorithm: 'RS256', + issuer: 'wrong', }; const invalidEntry2 = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['HS256'], - issuers: [], - audiences: ['backstage'], + algorithm: ['HS256'], + audience: 'wrong', }; const jwksHandler = new JWKSHandler(); @@ -201,9 +199,9 @@ describe('JWKSHandler', () => { it('uses custom subject prefix if provided', async () => { const validEntry = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['RS256'], - issuers: [mockBaseUrl], - audiences: ['backstage'], + algorithm: 'RS256', + issuer: mockBaseUrl, + audience: 'backstage', subjectPrefix: 'custom-prefix', }; const jwksHandler = new JWKSHandler(); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index ea62b3880c..8af44f4d54 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -16,6 +16,7 @@ import { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose'; import { Config } from '@backstage/config'; +import { readStringOrStringArrayFromConfig } from './helpers'; import { TokenHandler } from './types'; /** @@ -34,9 +35,9 @@ export class JWKSHandler implements TokenHandler { }> = []; add(options: Config) { - const algorithms = options.getOptionalStringArray('algorithms'); - const issuers = options.getOptionalStringArray('issuers'); - const audiences = options.getOptionalStringArray('audiences'); + const algorithms = readStringOrStringArrayFromConfig(options, 'algorithm'); + const issuers = readStringOrStringArrayFromConfig(options, 'issuer'); + const audiences = readStringOrStringArrayFromConfig(options, 'audience'); const subjectPrefix = options.getOptionalString('subjectPrefix'); const url = new URL(options.getString('url')); const jwks = createRemoteJWKSet(url); From 805cbe7970854ff698b5ab49aaba7a0cdc9bc21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 22 May 2024 14:43:20 +0200 Subject: [PATCH 071/118] add cache testing utilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/cyan-jobs-visit.md | 5 + .github/workflows/ci.yml | 4 +- .github/workflows/deploy_packages.yml | 10 + ...tegration.test.ts => CacheManager.test.ts} | 63 +++--- packages/backend-test-utils/api-report.md | 23 ++ packages/backend-test-utils/package.json | 4 + .../src/cache/TestCaches.test.ts | 53 +++++ .../src/cache/TestCaches.ts | 210 ++++++++++++++++++ .../backend-test-utils/src/cache/index.ts | 18 ++ .../src/cache/memcache.test.ts | 34 +++ .../backend-test-utils/src/cache/memcache.ts | 83 +++++++ .../src/cache/redis.test.ts | 34 +++ .../backend-test-utils/src/cache/redis.ts | 81 +++++++ .../backend-test-utils/src/cache/types.ts | 61 +++++ .../backend-test-utils/src/database/index.ts | 1 - packages/backend-test-utils/src/index.ts | 1 + .../src/util/isDockerDisabledForTests.ts | 2 +- yarn.lock | 33 ++- 18 files changed, 678 insertions(+), 42 deletions(-) create mode 100644 .changeset/cyan-jobs-visit.md rename packages/backend-defaults/src/entrypoints/cache/{CacheManager.integration.test.ts => CacheManager.test.ts} (61%) create mode 100644 packages/backend-test-utils/src/cache/TestCaches.test.ts create mode 100644 packages/backend-test-utils/src/cache/TestCaches.ts create mode 100644 packages/backend-test-utils/src/cache/index.ts create mode 100644 packages/backend-test-utils/src/cache/memcache.test.ts create mode 100644 packages/backend-test-utils/src/cache/memcache.ts create mode 100644 packages/backend-test-utils/src/cache/redis.test.ts create mode 100644 packages/backend-test-utils/src/cache/redis.ts create mode 100644 packages/backend-test-utils/src/cache/types.ts diff --git a/.changeset/cyan-jobs-visit.md b/.changeset/cyan-jobs-visit.md new file mode 100644 index 0000000000..ccc8c90adf --- /dev/null +++ b/.changeset/cyan-jobs-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': minor +--- + +Added `TestCaches` that functions just like `TestDatabases` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45d2363a36..fd3b66ddaa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,7 +188,7 @@ jobs: ports: - 3306/tcp redis: - image: redis + image: redis:7 options: >- --health-cmd "redis-cli ping" --health-interval 10s @@ -240,7 +240,7 @@ jobs: BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }} BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }} BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored - BACKSTAGE_TEST_CACHE_REDIS_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }} + BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }} # We run the test cases before verifying the specs to prevent any failing tests from causing errors. - name: verify openapi specs against test cases diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 444f3f825d..cd2947a565 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -55,6 +55,15 @@ jobs: --health-retries 5 ports: - 3306/tcp + redis: + image: redis:7 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379/tcp env: CI: true @@ -115,6 +124,7 @@ jobs: BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }} BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }} BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored + BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }} - name: Discord notification if: ${{ failure() }} diff --git a/packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts similarity index 61% rename from packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts index a10b02a62e..6eb907764d 100644 --- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { mockServices } from '@backstage/backend-test-utils'; +import { mockServices, TestCaches } from '@backstage/backend-test-utils'; import KeyvRedis from '@keyv/redis'; +import KeyvMemcache from '@keyv/memcache'; import { CacheManager } from './CacheManager'; // This test is in a separate file because the main test file uses other mocking @@ -23,28 +24,32 @@ import { CacheManager } from './CacheManager'; // Contrived code because it's hard to spy on a default export jest.mock('@keyv/redis', () => { - const ActualKeyvRedis = jest.requireActual('@keyv/redis'); + const Actual = jest.requireActual('@keyv/redis'); return jest.fn((...args: any[]) => { - return new ActualKeyvRedis(...args); + return new Actual(...args); + }); +}); +jest.mock('@keyv/memcache', () => { + const Actual = jest.requireActual('@keyv/memcache'); + return jest.fn((...args: any[]) => { + return new Actual(...args); }); }); describe('CacheManager integration', () => { - describe('redis', () => { - it('only creates one underlying connection', async () => { - const connection = - process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING; - if (!connection) { - return; - } + const caches = TestCaches.create(); + + afterEach(jest.clearAllMocks); + + it.each(caches.eachSupportedId())( + 'only creates one underlying connection, %p', + async cacheId => { + const { store, connection } = await caches.init(cacheId); const manager = CacheManager.fromConfig( mockServices.rootConfig({ - data: { - backend: { cache: { store: 'redis', connection } }, - }, + data: { backend: { cache: { store, connection } } }, }), - { onError: e => expect(e).not.toBeDefined() }, ); manager.forPlugin('p1').getClient(); @@ -52,25 +57,27 @@ describe('CacheManager integration', () => { manager.forPlugin('p2').getClient(); manager.forPlugin('p3').getClient({}); - expect(KeyvRedis).toHaveBeenCalledTimes(1); - }); - - it('interacts correctly with redis', async () => { - // TODO(freben): This could be frameworkified as TestCaches just like - // TestDatabases, but that will have to come some other day - const connection = - process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING; - if (!connection) { - return; + if (store === 'redis') { + // eslint-disable-next-line jest/no-conditional-expect + expect(KeyvRedis).toHaveBeenCalledTimes(1); + } else if (store === 'memcache') { + // eslint-disable-next-line jest/no-conditional-expect + expect(KeyvMemcache).toHaveBeenCalledTimes(1); } + }, + ); + + it.each(caches.eachSupportedId())( + 'interacts correctly with store, %p', + async cacheId => { + const { store, connection } = await caches.init(cacheId); const manager = CacheManager.fromConfig( mockServices.rootConfig({ data: { - backend: { cache: { store: 'redis', connection } }, + backend: { cache: { store, connection } }, }, }), - { onError: e => expect(e).not.toBeDefined() }, ); const plugin1 = manager.forPlugin('p1').getClient(); @@ -84,6 +91,6 @@ describe('CacheManager integration', () => { await expect(plugin1.get('a')).resolves.toBe('plugin1'); await expect(plugin2a.get('a')).resolves.toBe('plugin2b'); await expect(plugin2b.get('a')).resolves.toBe('plugin2b'); - }); - }); + }, + ); }); diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 4f6eb47243..21bbcc4e87 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -25,6 +25,7 @@ import { HttpRouterFactoryOptions } from '@backstage/backend-app-api'; import { HttpRouterService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; +import Keyv from 'keyv'; import { Knex } from 'knex'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -423,6 +424,28 @@ export interface TestBackendOptions { >; } +// @public +export type TestCacheId = 'MEMORY' | 'REDIS_7' | 'MEMCACHED_1'; + +// @public +export class TestCaches { + static create(options?: { + ids?: TestCacheId[]; + disableDocker?: boolean; + }): TestCaches; + // (undocumented) + eachSupportedId(): [TestCacheId][]; + init(id: TestCacheId): Promise<{ + store: string; + connection: string; + keyv: Keyv; + }>; + // (undocumented) + static setDefaults(options: { ids?: TestCacheId[] }): void; + // (undocumented) + supports(id: TestCacheId): boolean; +} + // @public export type TestDatabaseId = | 'POSTGRES_16' diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 00d5b3b4e4..c84956e58a 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -53,10 +53,14 @@ "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@backstage/types": "workspace:^", + "@keyv/memcache": "^1.3.5", + "@keyv/redis": "^2.5.3", + "@types/keyv": "^4.2.0", "better-sqlite3": "^9.0.0", "cookie": "^0.6.0", "express": "^4.17.1", "fs-extra": "^11.0.0", + "keyv": "^4.5.2", "knex": "^3.0.0", "msw": "^1.0.0", "mysql2": "^3.0.0", diff --git a/packages/backend-test-utils/src/cache/TestCaches.test.ts b/packages/backend-test-utils/src/cache/TestCaches.test.ts new file mode 100644 index 0000000000..7d5ef69704 --- /dev/null +++ b/packages/backend-test-utils/src/cache/TestCaches.test.ts @@ -0,0 +1,53 @@ +/* + * 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. + */ + +import { isDockerDisabledForTests } from '../util'; +import { TestCaches } from './TestCaches'; + +const itIfDocker = isDockerDisabledForTests() ? it.skip : it; + +jest.setTimeout(60_000); + +describe('TestCaches', () => { + const caches = TestCaches.create(); + + it.each(caches.eachSupportedId())('fires up a cache, %p', async cacheId => { + const { keyv } = await caches.init(cacheId); + await keyv.set('test', 'value'); + await expect(keyv.get('test')).resolves.toBe('value'); + }); + + itIfDocker('clears between tests, part 1', async () => { + const { keyv } = await caches.init('REDIS_7'); + // eslint-disable-next-line jest/no-standalone-expect + await expect(keyv.get('collision')).resolves.toBeUndefined(); + await keyv.set('collision', 'something'); + }); + + itIfDocker('clears between tests, part 2', async () => { + const { keyv } = await caches.init('REDIS_7'); + // eslint-disable-next-line jest/no-standalone-expect + await expect(keyv.get('collision')).resolves.toBeUndefined(); + await keyv.set('collision', 'something'); + }); + + itIfDocker('clears between tests, part 3', async () => { + const { keyv } = await caches.init('REDIS_7'); + // eslint-disable-next-line jest/no-standalone-expect + await expect(keyv.get('collision')).resolves.toBeUndefined(); + await keyv.set('collision', 'something'); + }); +}); diff --git a/packages/backend-test-utils/src/cache/TestCaches.ts b/packages/backend-test-utils/src/cache/TestCaches.ts new file mode 100644 index 0000000000..e9ba0e5c4f --- /dev/null +++ b/packages/backend-test-utils/src/cache/TestCaches.ts @@ -0,0 +1,210 @@ +/* + * 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. + */ + +import Keyv from 'keyv'; +import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests'; +import { connectToExternalMemcache, startMemcachedContainer } from './memcache'; +import { connectToExternalRedis, startRedisContainer } from './redis'; +import { Instance, TestCacheId, TestCacheProperties, allCaches } from './types'; + +/** + * Encapsulates the creation of ephemeral test cache instances for use inside + * unit or integration tests. + * + * @public + */ +export class TestCaches { + private readonly instanceById: Map; + private readonly supportedIds: TestCacheId[]; + private static defaultIds?: TestCacheId[]; + + /** + * Creates an empty `TestCaches` instance, and sets up Jest to clean up all of + * its acquired resources after all tests finish. + * + * You typically want to create just a single instance like this at the top of + * your test file or `describe` block, and then call `init` many times on that + * instance inside the individual tests. Spinning up a "physical" cache + * instance takes a considerable amount of time, slowing down tests. But + * wiping the contents of an instance using `init` is very fast. + */ + static create(options?: { + ids?: TestCacheId[]; + disableDocker?: boolean; + }): TestCaches { + const ids = options?.ids; + const disableDocker = options?.disableDocker ?? isDockerDisabledForTests(); + + let testCacheIds: TestCacheId[]; + if (ids) { + testCacheIds = ids; + } else if (TestCaches.defaultIds) { + testCacheIds = TestCaches.defaultIds; + } else { + testCacheIds = Object.keys(allCaches) as TestCacheId[]; + } + + const supportedIds = testCacheIds.filter(id => { + const properties = allCaches[id]; + if (!properties) { + return false; + } + // If the caller has set up the env with an explicit connection string, + // we'll assume that this target will work + if ( + properties.connectionStringEnvironmentVariableName && + process.env[properties.connectionStringEnvironmentVariableName] + ) { + return true; + } + // If the cache doesn't require docker at all, there's nothing to worry + // about + if (!properties.dockerImageName) { + return true; + } + // If the cache requires docker, but docker is disabled, we will fail. + if (disableDocker) { + return false; + } + return true; + }); + + const caches = new TestCaches(supportedIds); + + if (supportedIds.length > 0) { + afterAll(async () => { + await caches.shutdown(); + }); + } + + return caches; + } + + static setDefaults(options: { ids?: TestCacheId[] }) { + TestCaches.defaultIds = options.ids; + } + + private constructor(supportedIds: TestCacheId[]) { + this.instanceById = new Map(); + this.supportedIds = supportedIds; + } + + supports(id: TestCacheId): boolean { + return this.supportedIds.includes(id); + } + + eachSupportedId(): [TestCacheId][] { + return this.supportedIds.map(id => [id]); + } + + /** + * Returns a fresh, empty cache for the given driver. + * + * @param id - The ID of the cache to use, e.g. 'REDIS_7' + * @returns Cache connection properties + */ + async init( + id: TestCacheId, + ): Promise<{ store: string; connection: string; keyv: Keyv }> { + const properties = allCaches[id]; + if (!properties) { + const candidates = Object.keys(allCaches).join(', '); + throw new Error( + `Unknown test cache ${id}, possible values are ${candidates}`, + ); + } + if (!this.supportedIds.includes(id)) { + const candidates = this.supportedIds.join(', '); + throw new Error( + `Unsupported test cache ${id} for this environment, possible values are ${candidates}`, + ); + } + + // Ensure that a testcontainers instance is up for this ID + let instance: Instance | undefined = this.instanceById.get(id); + if (!instance) { + instance = await this.initAny(properties); + this.instanceById.set(id, instance); + } + + // Ensure that it's cleared of data from previous tests + await instance.keyv.clear(); + + return { + store: instance.store, + connection: instance.connection, + keyv: instance.keyv, + }; + } + + private async initAny(properties: TestCacheProperties): Promise { + switch (properties.store) { + case 'memcache': + return this.initMemcached(properties); + case 'redis': + return this.initRedis(properties); + case 'memory': + return { + store: 'memory', + connection: 'memory', + keyv: new Keyv(), + stop: async () => {}, + }; + default: + throw new Error(`Unknown cache store '${properties.store}'`); + } + } + + private async initMemcached( + properties: TestCacheProperties, + ): Promise { + // Use the connection string if provided + const envVarName = properties.connectionStringEnvironmentVariableName; + if (envVarName) { + const connectionString = process.env[envVarName]; + if (connectionString) { + return connectToExternalMemcache(connectionString); + } + } + + return await startMemcachedContainer(properties.dockerImageName!); + } + + private async initRedis(properties: TestCacheProperties): Promise { + // Use the connection string if provided + const envVarName = properties.connectionStringEnvironmentVariableName; + if (envVarName) { + const connectionString = process.env[envVarName]; + if (connectionString) { + return connectToExternalRedis(connectionString); + } + } + + return await startRedisContainer(properties.dockerImageName!); + } + + private async shutdown() { + const instances = [...this.instanceById.values()]; + this.instanceById.clear(); + await Promise.all( + instances.map(({ stop }) => + stop().catch(error => { + console.warn(`TestCaches: Failed to stop container`, { error }); + }), + ), + ); + } +} diff --git a/packages/backend-test-utils/src/cache/index.ts b/packages/backend-test-utils/src/cache/index.ts new file mode 100644 index 0000000000..46d95545bf --- /dev/null +++ b/packages/backend-test-utils/src/cache/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export { TestCaches } from './TestCaches'; +export type { TestCacheId } from './types'; diff --git a/packages/backend-test-utils/src/cache/memcache.test.ts b/packages/backend-test-utils/src/cache/memcache.test.ts new file mode 100644 index 0000000000..bd912ce9d2 --- /dev/null +++ b/packages/backend-test-utils/src/cache/memcache.test.ts @@ -0,0 +1,34 @@ +/* + * 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. + */ + +import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests'; +import { startMemcachedContainer } from './memcache'; +import { v4 as uuid } from 'uuid'; + +const itIfDocker = isDockerDisabledForTests() ? it.skip : it; + +jest.setTimeout(60_000); + +describe('startMemcachedContainer', () => { + itIfDocker('successfully launches the container', async () => { + const { stop, keyv } = await startMemcachedContainer('memcached:1'); + const value = uuid(); + await keyv.set('test', value); + // eslint-disable-next-line jest/no-standalone-expect + await expect(keyv.get('test')).resolves.toBe(value); + await stop(); + }); +}); diff --git a/packages/backend-test-utils/src/cache/memcache.ts b/packages/backend-test-utils/src/cache/memcache.ts new file mode 100644 index 0000000000..b7ac07cb13 --- /dev/null +++ b/packages/backend-test-utils/src/cache/memcache.ts @@ -0,0 +1,83 @@ +/* + * 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. + */ + +import Keyv from 'keyv'; +import KeyvMemcache from '@keyv/memcache'; +import { v4 as uuid } from 'uuid'; +import { Instance } from './types'; + +async function attemptMemcachedConnection(connection: string): Promise { + const startTime = Date.now(); + + for (;;) { + try { + const store = new KeyvMemcache(connection); + const keyv = new Keyv({ store }); + const value = uuid(); + await keyv.set('test', value); + if ((await keyv.get('test')) === value) { + return keyv; + } + } catch (e) { + if (Date.now() - startTime > 30_000) { + throw new Error( + `Timed out waiting for memcached to be ready for connections, ${e}`, + ); + } + } + + await new Promise(resolve => setTimeout(resolve, 100)); + } +} + +export async function connectToExternalMemcache( + connection: string, +): Promise { + const keyv = await attemptMemcachedConnection(connection); + return { + store: 'memcache', + connection, + keyv, + stop: async () => await keyv.disconnect(), + }; +} + +export async function startMemcachedContainer( + image: string, +): Promise { + // Lazy-load to avoid side-effect of importing testcontainers + const { GenericContainer } = await import('testcontainers'); + + const container = await new GenericContainer(image) + .withExposedPorts(11211) + .start(); + + const host = container.getHost(); + const port = container.getMappedPort(11211); + const connection = `${host}:${port}`; + + const keyv = await attemptMemcachedConnection(connection); + + return { + store: 'memcache', + connection, + keyv, + stop: async () => { + await keyv.disconnect(); + await container.stop({ timeout: 10_000 }); + }, + }; +} diff --git a/packages/backend-test-utils/src/cache/redis.test.ts b/packages/backend-test-utils/src/cache/redis.test.ts new file mode 100644 index 0000000000..6555a26677 --- /dev/null +++ b/packages/backend-test-utils/src/cache/redis.test.ts @@ -0,0 +1,34 @@ +/* + * 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. + */ + +import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests'; +import { startRedisContainer } from './redis'; +import { v4 as uuid } from 'uuid'; + +const itIfDocker = isDockerDisabledForTests() ? it.skip : it; + +jest.setTimeout(60_000); + +describe('startRedisContainer', () => { + itIfDocker('successfully launches the container', async () => { + const { stop, keyv } = await startRedisContainer('redis:7'); + const value = uuid(); + await keyv.set('test', value); + // eslint-disable-next-line jest/no-standalone-expect + await expect(keyv.get('test')).resolves.toBe(value); + await stop(); + }); +}); diff --git a/packages/backend-test-utils/src/cache/redis.ts b/packages/backend-test-utils/src/cache/redis.ts new file mode 100644 index 0000000000..6185e4d076 --- /dev/null +++ b/packages/backend-test-utils/src/cache/redis.ts @@ -0,0 +1,81 @@ +/* + * 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. + */ + +import Keyv from 'keyv'; +import KeyvRedis from '@keyv/redis'; +import { v4 as uuid } from 'uuid'; +import { Instance } from './types'; + +async function attemptRedisConnection(connection: string): Promise { + const startTime = Date.now(); + + for (;;) { + try { + const store = new KeyvRedis(connection); + const keyv = new Keyv({ store }); + const value = uuid(); + await keyv.set('test', value); + if ((await keyv.get('test')) === value) { + return keyv; + } + } catch (e) { + if (Date.now() - startTime > 30_000) { + throw new Error( + `Timed out waiting for redis to be ready for connections, ${e}`, + ); + } + } + + await new Promise(resolve => setTimeout(resolve, 100)); + } +} + +export async function connectToExternalRedis( + connection: string, +): Promise { + const keyv = await attemptRedisConnection(connection); + return { + store: 'redis', + connection, + keyv, + stop: async () => await keyv.disconnect(), + }; +} + +export async function startRedisContainer(image: string): Promise { + // Lazy-load to avoid side-effect of importing testcontainers + const { GenericContainer } = await import('testcontainers'); + + const container = await new GenericContainer(image) + .withExposedPorts(6379) + .start(); + + const host = container.getHost(); + const port = container.getMappedPort(6379); + const connection = `redis://${host}:${port}`; + + const keyv = await attemptRedisConnection(connection); + + return { + store: 'redis', + connection, + keyv, + stop: async () => { + await keyv.disconnect(); + await container.stop({ timeout: 10_000 }); + }, + }; +} diff --git a/packages/backend-test-utils/src/cache/types.ts b/packages/backend-test-utils/src/cache/types.ts new file mode 100644 index 0000000000..1aea522042 --- /dev/null +++ b/packages/backend-test-utils/src/cache/types.ts @@ -0,0 +1,61 @@ +/* + * 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. + */ + +import Keyv from 'keyv'; +import { getDockerImageForName } from '../util/getDockerImageForName'; + +/** + * The possible caches to test against. + * + * @public + */ +export type TestCacheId = 'MEMORY' | 'REDIS_7' | 'MEMCACHED_1'; + +export type TestCacheProperties = { + name: string; + store: string; + dockerImageName?: string; + connectionStringEnvironmentVariableName?: string; +}; + +export type Instance = { + store: string; + connection: string; + keyv: Keyv; + stop: () => Promise; +}; + +export const allCaches: Record = + Object.freeze({ + REDIS_7: { + name: 'Redis 7.x', + store: 'redis', + dockerImageName: getDockerImageForName('redis:7'), + connectionStringEnvironmentVariableName: + 'BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING', + }, + MEMCACHED_1: { + name: 'Memcached 1.x', + store: 'memcache', + dockerImageName: getDockerImageForName('memcached:1'), + connectionStringEnvironmentVariableName: + 'BACKSTAGE_TEST_CACHE_MEMCACHED1_CONNECTION_STRING', + }, + MEMORY: { + name: 'In-memory', + store: 'memory', + }, + }); diff --git a/packages/backend-test-utils/src/database/index.ts b/packages/backend-test-utils/src/database/index.ts index 69e3f41452..949553cef2 100644 --- a/packages/backend-test-utils/src/database/index.ts +++ b/packages/backend-test-utils/src/database/index.ts @@ -14,6 +14,5 @@ * limitations under the License. */ -export { isDockerDisabledForTests } from '../util/isDockerDisabledForTests'; export { TestDatabases } from './TestDatabases'; export type { TestDatabaseId } from './types'; diff --git a/packages/backend-test-utils/src/index.ts b/packages/backend-test-utils/src/index.ts index ff1f2ee460..72abd908c5 100644 --- a/packages/backend-test-utils/src/index.ts +++ b/packages/backend-test-utils/src/index.ts @@ -20,6 +20,7 @@ * @packageDocumentation */ +export * from './cache'; export * from './database'; export * from './msw'; export * from './filesystem'; diff --git a/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts b/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts index b411086728..05eeabd91a 100644 --- a/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts +++ b/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts @@ -20,7 +20,7 @@ export function isDockerDisabledForTests() { // the (relatively heavy, long running) docker based tests. If you want to // still run local tests for all databases, just pass either the CI=1 env // parameter to your test runner, or individual connection strings per - // database. + // database or cache. return ( Boolean(process.env.BACKSTAGE_TEST_DISABLE_DOCKER) || !Boolean(process.env.CI) diff --git a/yarn.lock b/yarn.lock index 157f44465e..5a9f99ac22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3582,11 +3582,15 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@backstage/types": "workspace:^" + "@keyv/memcache": ^1.3.5 + "@keyv/redis": ^2.5.3 + "@types/keyv": ^4.2.0 "@types/supertest": ^2.0.8 better-sqlite3: ^9.0.0 cookie: ^0.6.0 express: ^4.17.1 fs-extra: ^11.0.0 + keyv: ^4.5.2 knex: ^3.0.0 msw: ^1.0.0 mysql2: ^3.0.0 @@ -17510,7 +17514,16 @@ __metadata: languageName: node linkType: hard -"@types/keyv@npm:*, @types/keyv@npm:^3.1.1": +"@types/keyv@npm:*, @types/keyv@npm:^4.2.0": + version: 4.2.0 + resolution: "@types/keyv@npm:4.2.0" + dependencies: + keyv: "*" + checksum: 8713da9382b9346d664866a6cab2f91b0fd479f61379af891303a618e9a2abad6f347adc38a0850540e3f2dad278427de24e7555339264fddb04d1d17d3b50e0 + languageName: node + linkType: hard + +"@types/keyv@npm:^3.1.1": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" dependencies: @@ -31023,6 +31036,15 @@ __metadata: languageName: node linkType: hard +"keyv@npm:*, keyv@npm:^4.0.0, keyv@npm:^4.5.2": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: 3.0.1 + checksum: 74a24395b1c34bd44ad5cb2b49140d087553e170625240b86755a6604cd65aa16efdbdeae5cdb17ba1284a0fbb25ad06263755dbc71b8d8b06f74232ce3cdd72 + languageName: node + linkType: hard + "keyv@npm:^3.0.0": version: 3.1.0 resolution: "keyv@npm:3.1.0" @@ -31032,15 +31054,6 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^4.0.0, keyv@npm:^4.5.2": - version: 4.5.4 - resolution: "keyv@npm:4.5.4" - dependencies: - json-buffer: 3.0.1 - checksum: 74a24395b1c34bd44ad5cb2b49140d087553e170625240b86755a6604cd65aa16efdbdeae5cdb17ba1284a0fbb25ad06263755dbc71b8d8b06f74232ce3cdd72 - languageName: node - linkType: hard - "kind-of@npm:^6.0.2, kind-of@npm:^6.0.3": version: 6.0.3 resolution: "kind-of@npm:6.0.3" From 539b1382afbb13b296d941d411b8b506d03abad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 23 May 2024 09:49:53 +0200 Subject: [PATCH 072/118] improve the config test too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/proxy-backend/package.json | 4 +- .../src/service/router.config.test.ts | 111 ++++++++---------- .../src/service/router.credentials.test.ts | 4 +- yarn.lock | 2 - 4 files changed, 53 insertions(+), 68 deletions(-) diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 1ca6cbe5f5..3d3ccfaa0d 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -71,13 +71,11 @@ "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", "@types/http-proxy-middleware": "^1.0.0", - "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", "@types/yup": "^0.32.0", "msw": "^2.0.0", "node-fetch": "^2.6.7", - "portfinder": "^1.0.32", - "supertest": "^6.1.3" + "portfinder": "^1.0.32" }, "configSchema": "config.d.ts" } diff --git a/plugins/proxy-backend/src/service/router.config.test.ts b/plugins/proxy-backend/src/service/router.config.test.ts index 20aed5452a..c868092612 100644 --- a/plugins/proxy-backend/src/service/router.config.test.ts +++ b/plugins/proxy-backend/src/service/router.config.test.ts @@ -14,51 +14,46 @@ * limitations under the License. */ +import { createBackend } from '@backstage/backend-defaults'; import { - HostDiscovery, - loggerToWinstonLogger, -} from '@backstage/backend-common'; + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigSources, MutableConfigSource, StaticConfigSource, } from '@backstage/config-loader'; -import express from 'express'; -import { http, HttpResponse } from 'msw'; +import { HttpResponse, http, passthrough } from 'msw'; import { setupServer } from 'msw/node'; -import request from 'supertest'; -import { createRouter } from './router'; -import { mockServices } from '@backstage/backend-test-utils'; +import fetch from 'node-fetch'; +import portFinder from 'portfinder'; // this test is stored in its own file to work around the mocked // http-proxy-middleware module used in the main test file describe('createRouter reloadable configuration', () => { - const server = setupServer( - http.get('https://non-existing-example.com/', req => - HttpResponse.json({ - url: req.request.url.toString(), - headers: req.request.headers, - }), - ), - ); - - beforeAll(() => - server.listen({ - onUnhandledRequest: ({ headers }, print) => { - if (headers.get('User-Agent') === 'supertest') { - return; - } - print.error(); - }, - }), - ); - - afterAll(() => server.close()); - afterEach(() => server.resetHandlers()); + const server = setupServer(); + setupRequestMockHandlers(server); it('should be able to observe the config', async () => { - const logger = loggerToWinstonLogger(mockServices.logger.mock()); + const host = 'localhost'; + const port = await portFinder.getPortPromise({ host }); + const baseUrl = `http://${host}:${port}`; + + server.use( + http.all(`${baseUrl}/*`, passthrough), + http.get('https://non-existing-example.com/*', req => + HttpResponse.json({ + url: req.request.url.toString(), + headers: req.request.headers, + }), + ), + ); // Grab the subscriber function and use mutable config data to mock a config file change const mutableConfigSource = MutableConfigSource.create({ data: {} }); @@ -67,18 +62,14 @@ describe('createRouter reloadable configuration', () => { StaticConfigSource.create({ data: { backend: { - baseUrl: 'http://localhost:7007', - listen: { - port: 7007, - }, + baseUrl, + listen: { host, port }, }, proxy: { endpoints: { '/test': { target: 'https://non-existing-example.com', - pathRewrite: { - '.*': '/', - }, + credentials: 'dangerously-allow-unauthenticated', }, }, }, @@ -88,40 +79,38 @@ describe('createRouter reloadable configuration', () => { ]), ); - const discovery = HostDiscovery.fromConfig(config); - const router = await createRouter({ - config, - logger, - discovery, + const backend = createBackend(); + backend.add(import('../alpha')); + backend.add( + createServiceFactory({ + service: coreServices.rootConfig, + deps: {}, + factory: () => config, + }), + ); + backend.add(mockServices.rootLogger.factory()); + await backend.start(); + + await expect(fetch(`${baseUrl}/api/proxy/test`)).resolves.toMatchObject({ + status: 200, }); - expect(router).toBeDefined(); - - const app = express(); - app.use(router); - - const agent = request.agent(app); - // this is set to let msw pass test requests through the mock server - agent.set('User-Agent', 'supertest'); - - const response1 = await agent.get('/test'); - - expect(response1.status).toEqual(200); + await expect( + fetch(`${baseUrl}/api/proxy/test2`), + ).resolves.not.toMatchObject({ status: 200 }); mutableConfigSource.setData({ proxy: { endpoints: { '/test2': { target: 'https://non-existing-example.com', - pathRewrite: { - '.*': '/', - }, + credentials: 'dangerously-allow-unauthenticated', }, }, }, }); - const response2 = await agent.get('/test2'); - - expect(response2.status).toEqual(200); + await expect(fetch(`${baseUrl}/api/proxy/test2`)).resolves.toMatchObject({ + status: 200, + }); }); }); diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts index 85fad9f038..53d0f157f3 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -35,7 +35,7 @@ describe('credentials', () => { it('handles all valid credentials settings', async () => { const host = 'localhost'; - const port = await portFinder.getPortPromise(); + const port = await portFinder.getPortPromise({ host }); const baseUrl = `http://${host}:${port}`; const config = { @@ -82,7 +82,7 @@ describe('credentials', () => { }; worker.use( - http.all(`${baseUrl}/*`, () => passthrough()), + http.all(`${baseUrl}/*`, passthrough), http.get('http://target.com/*', req => { const auth = req.request.headers.get('authorization'); return HttpResponse.json({ diff --git a/yarn.lock b/yarn.lock index a823457dce..3288ed9a2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6464,7 +6464,6 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": ^4.17.6 "@types/http-proxy-middleware": ^1.0.0 - "@types/supertest": ^2.0.8 "@types/uuid": ^9.0.0 "@types/yup": ^0.32.0 express: ^4.17.1 @@ -6474,7 +6473,6 @@ __metadata: msw: ^2.0.0 node-fetch: ^2.6.7 portfinder: ^1.0.32 - supertest: ^6.1.3 uuid: ^9.0.0 winston: ^3.2.1 yaml: ^2.0.0 From 206ce36aa244e511627b7ef9938f3af82b16135c Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Thu, 23 May 2024 10:39:51 +0200 Subject: [PATCH 073/118] Use fetch Signed-off-by: Alex Eftimie --- .../src/api/CatalogImportClient.test.ts | 19 ++---- .../src/api/CatalogImportClient.ts | 54 ++++++++--------- .../src/api/KubernetesBackendClient.test.ts | 6 +- .../src/api/KubernetesBackendClient.ts | 28 +++------ plugins/search/src/alpha.tsx | 7 ++- plugins/search/src/apis.test.ts | 60 ++++++++++--------- plugins/search/src/apis.ts | 16 ++--- plugins/search/src/plugin.ts | 8 +-- 8 files changed, 86 insertions(+), 112 deletions(-) diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index df44ec94c4..98cf972446 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -50,7 +50,7 @@ import { ConfigReader, UrlPatternDiscovery } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { ScmAuthApi } from '@backstage/integration-react'; import { CatalogApi } from '@backstage/plugin-catalog-react'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils'; import { Octokit } from '@octokit/rest'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -66,14 +66,7 @@ describe('CatalogImportClient', () => { const scmAuthApi: jest.Mocked = { getCredentials: jest.fn().mockResolvedValue({ token: 'token' }), }; - const identityApi = { - signOut: () => { - return Promise.resolve(); - }, - getProfileInfo: jest.fn(), - getBackstageIdentity: jest.fn(), - getCredentials: jest.fn().mockResolvedValue({ token: 'token' }), - }; + const fetchApi = new MockFetchApi(); const scmIntegrationsApi = ScmIntegrations.fromConfig( new ConfigReader({ @@ -110,7 +103,7 @@ describe('CatalogImportClient', () => { discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi: catalogApi as Partial as CatalogApi, configApi: new ConfigReader({ app: { @@ -456,7 +449,7 @@ describe('CatalogImportClient', () => { discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi: catalogApi as Partial as CatalogApi, configApi: new ConfigReader({ catalog: { @@ -659,7 +652,7 @@ describe('CatalogImportClient', () => { discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi: catalogApi as Partial as CatalogApi, configApi: new ConfigReader({ catalog: { @@ -745,7 +738,7 @@ describe('CatalogImportClient', () => { discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi: catalogApi as Partial as CatalogApi, configApi: new ConfigReader({ catalog: { diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index cecb85f9a3..ec5950ec51 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -15,11 +15,7 @@ */ import { CatalogApi } from '@backstage/catalog-client'; -import { - ConfigApi, - DiscoveryApi, - IdentityApi, -} from '@backstage/core-plugin-api'; +import { ConfigApi, DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; import { GithubIntegrationConfig, ScmIntegrationRegistry, @@ -41,7 +37,7 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; */ export class CatalogImportClient implements CatalogImportApi { private readonly discoveryApi: DiscoveryApi; - private readonly identityApi: IdentityApi; + private readonly fetchApi: FetchApi; private readonly scmAuthApi: ScmAuthApi; private readonly scmIntegrationsApi: ScmIntegrationRegistry; private readonly catalogApi: CatalogApi; @@ -50,14 +46,14 @@ export class CatalogImportClient implements CatalogImportApi { constructor(options: { discoveryApi: DiscoveryApi; scmAuthApi: ScmAuthApi; - identityApi: IdentityApi; + fetchApi: FetchApi; scmIntegrationsApi: ScmIntegrationRegistry; catalogApi: CatalogApi; configApi: ConfigApi; }) { this.discoveryApi = options.discoveryApi; this.scmAuthApi = options.scmAuthApi; - this.identityApi = options.identityApi; + this.fetchApi = options.fetchApi; this.scmIntegrationsApi = options.scmIntegrationsApi; this.catalogApi = options.catalogApi; this.configApi = options.configApi; @@ -206,29 +202,29 @@ the component will become available.\n\nFor more information, read an \ private async analyzeLocation(options: { repo: string; }): Promise { - const { token } = await this.identityApi.getCredentials(); - const response = await fetch( - `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`, - { - headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), - }, - method: 'POST', - body: JSON.stringify({ - location: { type: 'url', target: options.repo }, - ...(this.configApi.getOptionalString( - 'catalog.import.entityFilename', - ) && { - catalogFilename: this.configApi.getOptionalString( + const response = await this.fetchApi + .fetch( + `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`, + { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ + location: { type: 'url', target: options.repo }, + ...(this.configApi.getOptionalString( 'catalog.import.entityFilename', - ), + ) && { + catalogFilename: this.configApi.getOptionalString( + 'catalog.import.entityFilename', + ), + }), }), - }), - }, - ).catch(e => { - throw new Error(`Failed to generate entity definitions, ${e.message}`); - }); + }, + ) + .catch(e => { + throw new Error(`Failed to generate entity definitions, ${e.message}`); + }); if (!response.ok) { throw new Error( `Failed to generate entity definitions. Received http response ${response.status}: ${response.statusText}`, diff --git a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts index 0535e404f6..82ac7c84e2 100644 --- a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts +++ b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts @@ -19,7 +19,7 @@ import { KubernetesBackendClient } from './KubernetesBackendClient'; import { rest } from 'msw'; import { UrlPatternDiscovery } from '@backstage/core-app-api'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils'; import { CustomObjectsByEntityRequest, KubernetesRequestBody, @@ -44,6 +44,7 @@ describe('KubernetesBackendClient', () => { getBackstageIdentity: jest.fn(), signOut: jest.fn(), }; + const fetchApi = new MockFetchApi({ injectIdentityAuth: { identityApi } }); beforeEach(() => { jest.resetAllMocks(); @@ -51,7 +52,7 @@ describe('KubernetesBackendClient', () => { discoveryApi: UrlPatternDiscovery.compile( 'http://localhost:1234/api/{{ pluginId }}', ), - identityApi, + fetchApi, kubernetesAuthProvidersApi, }); mockResponse = { @@ -454,6 +455,7 @@ describe('KubernetesBackendClient', () => { }); it('hits the /proxy API with serviceAccount as auth provider', async () => { + identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( rest.get( 'http://localhost:1234/api/kubernetes/clusters', diff --git a/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts b/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts index 461b1d1ffe..f0bd0fb945 100644 --- a/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts @@ -21,7 +21,7 @@ import { WorkloadsByEntityRequest, CustomObjectsByEntityRequest, } from '@backstage/plugin-kubernetes-common'; -import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; +import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { KubernetesAuthProvidersApi } from '../kubernetes-auth-provider'; import { NotFoundError } from '@backstage/errors'; @@ -29,16 +29,16 @@ import { NotFoundError } from '@backstage/errors'; /** @public */ export class KubernetesBackendClient implements KubernetesApi { private readonly discoveryApi: DiscoveryApi; - private readonly identityApi: IdentityApi; + private readonly fetchApi: FetchApi; private readonly kubernetesAuthProvidersApi: KubernetesAuthProvidersApi; constructor(options: { discoveryApi: DiscoveryApi; - identityApi: IdentityApi; + fetchApi: FetchApi; kubernetesAuthProvidersApi: KubernetesAuthProvidersApi; }) { this.discoveryApi = options.discoveryApi; - this.identityApi = options.identityApi; + this.fetchApi = options.fetchApi; this.kubernetesAuthProvidersApi = options.kubernetesAuthProvidersApi; } @@ -62,12 +62,10 @@ export class KubernetesBackendClient implements KubernetesApi { private async postRequired(path: string, requestBody: any): Promise { const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`; - const { token: idToken } = await this.identityApi.getCredentials(); - const response = await fetch(url, { + const response = await this.fetchApi.fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', - ...(idToken && { Authorization: `Bearer ${idToken}` }), }, body: JSON.stringify(requestBody), }); @@ -130,14 +128,8 @@ export class KubernetesBackendClient implements KubernetesApi { } async getClusters(): Promise<{ name: string; authProvider: string }[]> { - const { token: idToken } = await this.identityApi.getCredentials(); const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/clusters`; - const response = await fetch(url, { - method: 'GET', - headers: { - ...(idToken && { Authorization: `Bearer ${idToken}` }), - }, - }); + const response = await this.fetchApi.fetch(url); return (await this.handleResponse(response)).items; } @@ -157,15 +149,13 @@ export class KubernetesBackendClient implements KubernetesApi { const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/proxy${ options.path }`; - const identityResponse = await this.identityApi.getCredentials(); const headers = KubernetesBackendClient.getKubernetesHeaders( options, kubernetesCredentials?.token, - identityResponse, authProvider, oidcTokenProvider, ); - return await fetch(url, { ...options.init, headers }); + return await this.fetchApi.fetch(url, { ...options.init, headers }); } private static getKubernetesHeaders( @@ -175,7 +165,6 @@ export class KubernetesBackendClient implements KubernetesApi { init?: RequestInit; }, k8sToken: string | undefined, - identityResponse: { token?: string }, authProvider: string, oidcTokenProvider: string | undefined, ) { @@ -190,9 +179,6 @@ export class KubernetesBackendClient implements KubernetesApi { ...(k8sToken && { [kubernetesAuthHeader]: k8sToken, }), - ...(identityResponse.token && { - Authorization: `Bearer ${identityResponse.token}`, - }), }; } diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index 9fffa41bfb..ca22a57c54 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -35,6 +35,7 @@ import { IdentityApi, discoveryApiRef, identityApiRef, + FetchApi, } from '@backstage/core-plugin-api'; import { @@ -81,12 +82,12 @@ export const searchApi = createApiExtension({ api: searchApiRef, deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef }, factory: ({ - identityApi, + fetchApi, discoveryApi, }: { - identityApi: IdentityApi; + fetchApi: FetchApi; discoveryApi: DiscoveryApi; - }) => new SearchClient({ discoveryApi, identityApi }), + }) => new SearchClient({ discoveryApi, fetchApi }), }, }); diff --git a/plugins/search/src/apis.test.ts b/plugins/search/src/apis.test.ts index 7996ad3fa3..5d0112c7c7 100644 --- a/plugins/search/src/apis.test.ts +++ b/plugins/search/src/apis.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { MockFetchApi } from '@backstage/test-utils'; import { SearchClient } from './apis'; describe('apis', () => { @@ -26,48 +27,49 @@ describe('apis', () => { const baseUrl = 'https://base-url.com/'; const getBaseUrl = jest.fn().mockResolvedValue(baseUrl); - const token = 'AUTHTOKEN'; - const withToken = jest.fn().mockResolvedValue({ token }); - const withoutToken = jest.fn().mockResolvedValue({ token: undefined }); - const createIdentityApiMock = (getCredentials: any) => ({ - signOut: jest.fn(), + const identityApi = { + getCredentials: jest.fn(), getProfileInfo: jest.fn(), getBackstageIdentity: jest.fn(), - getCredentials, + signOut: jest.fn(), + }; + const json = jest.fn(); + const mockFetch = jest.fn().mockResolvedValue({ + ok: true, + json, + }); + const fetchApi = new MockFetchApi({ + baseImplementation: mockFetch, + injectIdentityAuth: { identityApi }, }); const client = new SearchClient({ discoveryApi: { getBaseUrl }, - identityApi: createIdentityApiMock(withoutToken), - }); - - const json = jest.fn(); - const originalFetch = window.fetch; - window.fetch = jest.fn().mockResolvedValue({ json, ok: true }); - - afterAll(() => { - window.fetch = originalFetch; + fetchApi, }); it('Fetch is called with expected URL (including stringified Q params)', async () => { + identityApi.getCredentials.mockResolvedValue({}); await client.query(query); expect(getBaseUrl).toHaveBeenLastCalledWith('search'); - expect(fetch).toHaveBeenLastCalledWith(`${baseUrl}/query?term=`, { - headers: {}, - }); + expect(mockFetch).toHaveBeenLastCalledWith( + `${baseUrl}/query?term=`, + undefined, + ); }); - it('Sets Authorization if token is available', async () => { - const authedClient = new SearchClient({ - discoveryApi: { getBaseUrl }, - identityApi: createIdentityApiMock(withToken), - }); - await authedClient.query(query); - expect(getBaseUrl).toHaveBeenLastCalledWith('search'); - expect(fetch).toHaveBeenLastCalledWith(`${baseUrl}/query?term=`, { - headers: { Authorization: `Bearer ${token}` }, - }); - }); + // it('Sets Authorization if token is available', async () => { + // identityApi.getCredentials.mockResolvedValue({ token: 'token' }); + // await client.query(query); + // expect(getBaseUrl).toHaveBeenLastCalledWith('search'); + // expect(mockFetch).toHaveBeenLastCalledWith( + // expect.objectContaining({ + // agent: undefined, + // query: 'term=', + // headers: { authorization: ["Bearer token"] } + // }) + // ); + // }); it('Resolves JSON from fetch response', async () => { const result = { loading: false, error: '', value: {} }; diff --git a/plugins/search/src/apis.ts b/plugins/search/src/apis.ts index eb5f47a65c..b56d88a22c 100644 --- a/plugins/search/src/apis.ts +++ b/plugins/search/src/apis.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; +import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; import { SearchApi } from '@backstage/plugin-search-react'; import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common'; @@ -23,25 +23,19 @@ import qs from 'qs'; export class SearchClient implements SearchApi { private readonly discoveryApi: DiscoveryApi; - private readonly identityApi: IdentityApi; + private readonly fetchApi: FetchApi; - constructor(options: { - discoveryApi: DiscoveryApi; - identityApi: IdentityApi; - }) { + constructor(options: { discoveryApi: DiscoveryApi; fetchApi: FetchApi }) { this.discoveryApi = options.discoveryApi; - this.identityApi = options.identityApi; + this.fetchApi = options.fetchApi; } async query(query: SearchQuery): Promise { - const { token } = await this.identityApi.getCredentials(); const queryString = qs.stringify(query); const url = `${await this.discoveryApi.getBaseUrl( 'search', )}/query?${queryString}`; - const response = await fetch(url, { - headers: token ? { Authorization: `Bearer ${token}` } : {}, - }); + const response = await this.fetchApi.fetch(url); if (!response.ok) { throw await ResponseError.fromResponse(response); diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index 07a645f73e..1adfd2e684 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -23,7 +23,7 @@ import { createRoutableExtension, discoveryApiRef, createComponentExtension, - identityApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ @@ -38,9 +38,9 @@ export const searchPlugin = createPlugin({ apis: [ createApiFactory({ api: searchApiRef, - deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef }, - factory: ({ discoveryApi, identityApi }) => { - return new SearchClient({ discoveryApi, identityApi }); + deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, + factory: ({ discoveryApi, fetchApi }) => { + return new SearchClient({ discoveryApi, fetchApi }); }, }), ], From bbd19b7c45a218448850f5d0851684cd7f5d056a Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Thu, 23 May 2024 11:25:22 +0200 Subject: [PATCH 074/118] fix build Signed-off-by: Alex Eftimie --- plugins/catalog-import/api-report.md | 4 ++-- plugins/catalog-import/src/alpha.tsx | 8 +++---- .../DefaultImportPage.test.tsx | 20 +++-------------- .../components/ImportPage/ImportPage.test.tsx | 22 ++++--------------- plugins/catalog-import/src/plugin.ts | 8 +++---- plugins/kubernetes-react/api-report.md | 4 ++-- plugins/kubernetes/src/plugin.ts | 8 +++---- plugins/search/src/alpha.tsx | 1 - 8 files changed, 23 insertions(+), 52 deletions(-) diff --git a/plugins/catalog-import/api-report.md b/plugins/catalog-import/api-report.md index a513cfe87a..c61aa97b06 100644 --- a/plugins/catalog-import/api-report.md +++ b/plugins/catalog-import/api-report.md @@ -13,8 +13,8 @@ import { ConfigApi } from '@backstage/core-plugin-api'; import { Controller } from 'react-hook-form'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { FetchApi } from '@backstage/core-plugin-api'; import { FieldErrors } from 'react-hook-form'; -import { IdentityApi } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { default as React_2 } from 'react'; @@ -102,7 +102,7 @@ export class CatalogImportClient implements CatalogImportApi { constructor(options: { discoveryApi: DiscoveryApi; scmAuthApi: ScmAuthApi; - identityApi: IdentityApi; + fetchApi: FetchApi; scmIntegrationsApi: ScmIntegrationRegistry; catalogApi: CatalogApi; configApi: ConfigApi; diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 304ee3f272..795e697874 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -18,7 +18,7 @@ import { configApiRef, createApiFactory, discoveryApiRef, - identityApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; import { compatWrapper, @@ -55,7 +55,7 @@ const catalogImportApi = createApiExtension({ deps: { discoveryApi: discoveryApiRef, scmAuthApi: scmAuthApiRef, - identityApi: identityApiRef, + fetchApi: fetchApiRef, scmIntegrationsApi: scmIntegrationsApiRef, catalogApi: catalogApiRef, configApi: configApiRef, @@ -63,7 +63,7 @@ const catalogImportApi = createApiExtension({ factory: ({ discoveryApi, scmAuthApi, - identityApi, + fetchApi, scmIntegrationsApi, catalogApi, configApi, @@ -72,7 +72,7 @@ const catalogImportApi = createApiExtension({ discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi, configApi, }), diff --git a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx index a231563484..929594f22a 100644 --- a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx +++ b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx @@ -25,22 +25,8 @@ import { catalogImportApiRef, CatalogImportClient } from '../../api'; import { DefaultImportPage } from './DefaultImportPage'; describe('', () => { - const identityApi = { - getUserId: () => { - return 'user'; - }, - getProfile: () => { - return {}; - }, - getIdToken: () => { - return Promise.resolve('token'); - }, - signOut: () => { - return Promise.resolve(); - }, - getProfileInfo: jest.fn(), - getBackstageIdentity: jest.fn(), - getCredentials: jest.fn(), + const fetchApi = { + fetch: jest.fn(), }; let apis: TestApiRegistry; @@ -56,7 +42,7 @@ describe('', () => { scmAuthApi: { getCredentials: async () => ({ token: 'token', headers: {} }), }, - identityApi, + fetchApi, scmIntegrationsApi: {} as any, catalogApi: {} as any, configApi: {} as any, diff --git a/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx b/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx index 1b0ddcfc04..e1c4edb896 100644 --- a/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx +++ b/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx @@ -16,7 +16,7 @@ import { CatalogClient } from '@backstage/catalog-client'; import { ApiProvider, ConfigReader } from '@backstage/core-app-api'; -import { configApiRef } from '@backstage/core-plugin-api'; +import { FetchApi, configApiRef } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { screen } from '@testing-library/react'; @@ -31,22 +31,8 @@ jest.mock('react-router-dom', () => ({ })); describe('', () => { - const identityApi = { - getUserId: () => { - return 'user'; - }, - getProfile: () => { - return {}; - }, - getIdToken: () => { - return Promise.resolve('token'); - }, - signOut: () => { - return Promise.resolve(); - }, - getProfileInfo: jest.fn(), - getBackstageIdentity: jest.fn(), - getCredentials: jest.fn(), + const fetchApi: FetchApi = { + fetch: jest.fn(), }; let apis: TestApiRegistry; @@ -59,7 +45,7 @@ describe('', () => { catalogImportApiRef, new CatalogImportClient({ discoveryApi: {} as any, - identityApi, + fetchApi, scmAuthApi: {} as any, scmIntegrationsApi: {} as any, catalogApi: {} as any, diff --git a/plugins/catalog-import/src/plugin.ts b/plugins/catalog-import/src/plugin.ts index d9dd226792..62741c27c0 100644 --- a/plugins/catalog-import/src/plugin.ts +++ b/plugins/catalog-import/src/plugin.ts @@ -21,7 +21,7 @@ import { createRoutableExtension, createRouteRef, discoveryApiRef, - identityApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; import { scmAuthApiRef, @@ -48,7 +48,7 @@ export const catalogImportPlugin = createPlugin({ deps: { discoveryApi: discoveryApiRef, scmAuthApi: scmAuthApiRef, - identityApi: identityApiRef, + fetchApi: fetchApiRef, scmIntegrationsApi: scmIntegrationsApiRef, catalogApi: catalogApiRef, configApi: configApiRef, @@ -56,7 +56,7 @@ export const catalogImportPlugin = createPlugin({ factory: ({ discoveryApi, scmAuthApi, - identityApi, + fetchApi, scmIntegrationsApi, catalogApi, configApi, @@ -65,7 +65,7 @@ export const catalogImportPlugin = createPlugin({ discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi, configApi, }), diff --git a/plugins/kubernetes-react/api-report.md b/plugins/kubernetes-react/api-report.md index ccc2e1643d..badf80a87f 100644 --- a/plugins/kubernetes-react/api-report.md +++ b/plugins/kubernetes-react/api-report.md @@ -16,10 +16,10 @@ import { DetectedErrorsByCluster } from '@backstage/plugin-kubernetes-common'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { Event as Event_2 } from 'kubernetes-models/v1'; +import { FetchApi } from '@backstage/core-plugin-api'; import { GroupedResponses } from '@backstage/plugin-kubernetes-common'; import { IContainer } from 'kubernetes-models/v1'; import { IContainerStatus } from 'kubernetes-models/v1'; -import { IdentityApi } from '@backstage/core-plugin-api'; import { IIoK8sApimachineryPkgApisMetaV1ObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta'; import { IObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta'; import { JsonObject } from '@backstage/types'; @@ -402,7 +402,7 @@ export const kubernetesAuthProvidersApiRef: ApiRef; export class KubernetesBackendClient implements KubernetesApi { constructor(options: { discoveryApi: DiscoveryApi; - identityApi: IdentityApi; + fetchApi: FetchApi; kubernetesAuthProvidersApi: KubernetesAuthProvidersApi; }); // (undocumented) diff --git a/plugins/kubernetes/src/plugin.ts b/plugins/kubernetes/src/plugin.ts index afe2287633..eaa714dbea 100644 --- a/plugins/kubernetes/src/plugin.ts +++ b/plugins/kubernetes/src/plugin.ts @@ -30,13 +30,13 @@ import { createPlugin, createRouteRef, discoveryApiRef, - identityApiRef, gitlabAuthApiRef, googleAuthApiRef, microsoftAuthApiRef, oktaAuthApiRef, oneloginAuthApiRef, createRoutableExtension, + fetchApiRef, } from '@backstage/core-plugin-api'; export const rootCatalogKubernetesRouteRef = createRouteRef({ @@ -50,13 +50,13 @@ export const kubernetesPlugin = createPlugin({ api: kubernetesApiRef, deps: { discoveryApi: discoveryApiRef, - identityApi: identityApiRef, + fetchApi: fetchApiRef, kubernetesAuthProvidersApi: kubernetesAuthProvidersApiRef, }, - factory: ({ discoveryApi, identityApi, kubernetesAuthProvidersApi }) => + factory: ({ discoveryApi, fetchApi, kubernetesAuthProvidersApi }) => new KubernetesBackendClient({ discoveryApi, - identityApi, + fetchApi, kubernetesAuthProvidersApi, }), }), diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index ca22a57c54..5c85d097d4 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -32,7 +32,6 @@ import { import { useApi, DiscoveryApi, - IdentityApi, discoveryApiRef, identityApiRef, FetchApi, From 4f92394b1a0511cc77fefc9946e415da925f89f7 Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Thu, 23 May 2024 11:26:22 +0200 Subject: [PATCH 075/118] Add changeset Signed-off-by: Alex Eftimie --- .changeset/empty-tables-ring.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/empty-tables-ring.md diff --git a/.changeset/empty-tables-ring.md b/.changeset/empty-tables-ring.md new file mode 100644 index 0000000000..1b52801ae0 --- /dev/null +++ b/.changeset/empty-tables-ring.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-kubernetes-react': minor +'@backstage/plugin-catalog-import': minor +'@backstage/plugin-kubernetes': minor +'@backstage/plugin-search': minor +--- + +Migrate from identityApi to fetchApi in frontend plugins. From 6d196b4506e002552b6d9258d2c639c33f86e7b9 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Thu, 23 May 2024 11:26:40 +0200 Subject: [PATCH 076/118] fix: Avoid infinite loop in the NotificationsSidebarItem title counter Signed-off-by: Marek Libra --- .changeset/little-cooks-approve.md | 5 +++ .../src/hooks/useTitleCounter.ts | 31 ++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) create mode 100644 .changeset/little-cooks-approve.md diff --git a/.changeset/little-cooks-approve.md b/.changeset/little-cooks-approve.md new file mode 100644 index 0000000000..d4088208ae --- /dev/null +++ b/.changeset/little-cooks-approve.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Fixes performance issue with Notifications title counter. diff --git a/plugins/notifications/src/hooks/useTitleCounter.ts b/plugins/notifications/src/hooks/useTitleCounter.ts index d794678ea6..0793cd58d6 100644 --- a/plugins/notifications/src/hooks/useTitleCounter.ts +++ b/plugins/notifications/src/hooks/useTitleCounter.ts @@ -13,38 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import throttle from 'lodash/throttle'; + +const getPrefix = (value: number) => (value === 0 ? '' : `(${value}) `); + +const cleanTitle = (currentTitle: string) => + currentTitle.replace(/^\(\d+\)\s/, ''); + +const throttledSetTitle = throttle((shownTitle: string) => { + document.title = shownTitle; +}, 100); /** @public */ export function useTitleCounter() { const [title, setTitle] = useState(document.title); const [count, setCount] = useState(0); - const titleTimer = useRef(undefined); - - const getPrefix = (value: number) => { - return value === 0 ? '' : `(${value}) `; - }; - - const cleanTitle = (currentTitle: string) => { - return currentTitle.replace(/^\(\d+\)\s/, ''); - }; useEffect(() => { const baseTitle = cleanTitle(title); const shownTitle = `${getPrefix(count)}${baseTitle}`; if (document.title !== shownTitle) { - window.clearTimeout(titleTimer.current); - document.title = shownTitle; - // Need to do this in timeout as the React Helmet overrides the title after this effect - titleTimer.current = window.setTimeout(() => { - document.title = shownTitle; - }, 50); + throttledSetTitle(shownTitle); } return () => { - window.clearTimeout(titleTimer.current); document.title = cleanTitle(title); }; - }, [title, count]); + }, [count, title]); useEffect(() => { const titleElement = document.querySelector('title'); From fdcaf5d93829f5a2f23fe756a9d1b22aa3563262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 23 May 2024 11:37:47 +0200 Subject: [PATCH 077/118] move to startTestBackend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/proxy-backend/package.json | 4 +- .../src/service/router.config.test.ts | 93 +++++++++---------- .../src/service/router.credentials.test.ts | 83 ++++++++--------- yarn.lock | 2 +- 4 files changed, 87 insertions(+), 95 deletions(-) diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 3d3ccfaa0d..af0d9d584e 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -65,6 +65,7 @@ "yup": "^1.0.0" }, "devDependencies": { + "@backstage/backend-app-api": "workspace:^", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", @@ -74,8 +75,7 @@ "@types/uuid": "^9.0.0", "@types/yup": "^0.32.0", "msw": "^2.0.0", - "node-fetch": "^2.6.7", - "portfinder": "^1.0.32" + "node-fetch": "^2.6.7" }, "configSchema": "config.d.ts" } diff --git a/plugins/proxy-backend/src/service/router.config.test.ts b/plugins/proxy-backend/src/service/router.config.test.ts index c868092612..ba81553a4f 100644 --- a/plugins/proxy-backend/src/service/router.config.test.ts +++ b/plugins/proxy-backend/src/service/router.config.test.ts @@ -14,14 +14,13 @@ * limitations under the License. */ -import { createBackend } from '@backstage/backend-defaults'; import { coreServices, createServiceFactory, } from '@backstage/backend-plugin-api'; import { - mockServices, setupRequestMockHandlers, + startTestBackend, } from '@backstage/backend-test-utils'; import { ConfigSources, @@ -31,7 +30,6 @@ import { import { HttpResponse, http, passthrough } from 'msw'; import { setupServer } from 'msw/node'; import fetch from 'node-fetch'; -import portFinder from 'portfinder'; // this test is stored in its own file to work around the mocked // http-proxy-middleware module used in the main test file @@ -41,30 +39,12 @@ describe('createRouter reloadable configuration', () => { setupRequestMockHandlers(server); it('should be able to observe the config', async () => { - const host = 'localhost'; - const port = await portFinder.getPortPromise({ host }); - const baseUrl = `http://${host}:${port}`; - - server.use( - http.all(`${baseUrl}/*`, passthrough), - http.get('https://non-existing-example.com/*', req => - HttpResponse.json({ - url: req.request.url.toString(), - headers: req.request.headers, - }), - ), - ); - // Grab the subscriber function and use mutable config data to mock a config file change const mutableConfigSource = MutableConfigSource.create({ data: {} }); const config = await ConfigSources.toConfig( ConfigSources.merge([ StaticConfigSource.create({ data: { - backend: { - baseUrl, - listen: { host, port }, - }, proxy: { endpoints: { '/test': { @@ -79,38 +59,53 @@ describe('createRouter reloadable configuration', () => { ]), ); - const backend = createBackend(); - backend.add(import('../alpha')); - backend.add( - createServiceFactory({ - service: coreServices.rootConfig, - deps: {}, - factory: () => config, - }), - ); - backend.add(mockServices.rootLogger.factory()); - await backend.start(); - - await expect(fetch(`${baseUrl}/api/proxy/test`)).resolves.toMatchObject({ - status: 200, + const backend = await startTestBackend({ + features: [ + import('../alpha'), + createServiceFactory({ + service: coreServices.rootConfig, + deps: {}, + factory: () => config, + }), + ], }); - await expect( - fetch(`${baseUrl}/api/proxy/test2`), - ).resolves.not.toMatchObject({ status: 200 }); - mutableConfigSource.setData({ - proxy: { - endpoints: { - '/test2': { - target: 'https://non-existing-example.com', - credentials: 'dangerously-allow-unauthenticated', + try { + const baseUrl = `http://localhost:${backend.server.port()}`; + + server.use( + http.all(`${baseUrl}/*`, passthrough), + http.get('https://non-existing-example.com/*', req => + HttpResponse.json({ + url: req.request.url.toString(), + headers: req.request.headers, + }), + ), + ); + + await expect(fetch(`${baseUrl}/api/proxy/test`)).resolves.toMatchObject({ + status: 200, + }); + await expect( + fetch(`${baseUrl}/api/proxy/test2`), + ).resolves.not.toMatchObject({ status: 200 }); + + mutableConfigSource.setData({ + proxy: { + endpoints: { + '/test2': { + target: 'https://non-existing-example.com', + credentials: 'dangerously-allow-unauthenticated', + }, }, }, - }, - }); + }); - await expect(fetch(`${baseUrl}/api/proxy/test2`)).resolves.toMatchObject({ - status: 200, - }); + await expect(fetch(`${baseUrl}/api/proxy/test2`)).resolves.toMatchObject({ + status: 200, + }); + } finally { + await backend.stop(); + } }); }); diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts index 53d0f157f3..54c865ba11 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -14,17 +14,20 @@ * limitations under the License. */ -import { createBackend } from '@backstage/backend-defaults'; +import { + authServiceFactory, + httpAuthServiceFactory, +} from '@backstage/backend-app-api'; import { mockServices, setupRequestMockHandlers, + startTestBackend, } from '@backstage/backend-test-utils'; import { ResponseError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; -import { http, HttpResponse, passthrough } from 'msw'; +import { HttpResponse, http, passthrough } from 'msw'; import { setupServer } from 'msw/node'; import fetch from 'node-fetch'; -import portFinder from 'portfinder'; // this test is stored in its own file to work around the mocked // http-proxy-middleware module used in the main test file @@ -34,14 +37,8 @@ describe('credentials', () => { setupRequestMockHandlers(worker); it('handles all valid credentials settings', async () => { - const host = 'localhost'; - const port = await portFinder.getPortPromise({ host }); - const baseUrl = `http://${host}:${port}`; - const config = { backend: { - baseUrl, - listen: { host, port }, auth: { externalAccess: [ { @@ -81,42 +78,42 @@ describe('credentials', () => { }, }; - worker.use( - http.all(`${baseUrl}/*`, passthrough), - http.get('http://target.com/*', req => { - const auth = req.request.headers.get('authorization'); - return HttpResponse.json({ - payload: { forwardedAuthorization: auth ?? false }, - }); - }), - ); - - async function call(options: { - endpoint: string; - authorization: string | false; - }): Promise { - const { endpoint, authorization } = options; - return fetch(`${baseUrl}/api/proxy/${endpoint}/just-some-path`, { - headers: authorization ? { Authorization: authorization } : {}, - }).then(async res => { - if (!res.ok) { - throw await ResponseError.fromResponse(res); - } - return res.json(); - }); - } - - // Create an actual backend instead of a test backend, because we want to - // use the real HTTP server that provides the protection middleware etc. A - // bit harder to test, but at least we can use static external access tokens - // for it. - const backend = createBackend(); - backend.add(import('../alpha')); - backend.add(mockServices.rootConfig.factory({ data: config })); - backend.add(mockServices.rootLogger.factory()); - await backend.start(); + const backend = await startTestBackend({ + features: [ + import('../alpha'), + mockServices.rootConfig.factory({ data: config }), + authServiceFactory(), + httpAuthServiceFactory(), + ], + }); try { + const baseUrl = `http://localhost:${backend.server.port()}`; + worker.use( + http.all(`${baseUrl}/*`, passthrough), + http.get('http://target.com/*', req => { + const auth = req.request.headers.get('authorization'); + return HttpResponse.json({ + payload: { forwardedAuthorization: auth ?? false }, + }); + }), + ); + + const call = async (options: { + endpoint: string; + authorization: string | false; + }): Promise => { + const { endpoint, authorization } = options; + return fetch(`${baseUrl}/api/proxy/${endpoint}/just-some-path`, { + headers: authorization ? { Authorization: authorization } : {}, + }).then(async res => { + if (!res.ok) { + throw await ResponseError.fromResponse(res); + } + return res.json(); + }); + }; + // simple credentials config await expect( call({ endpoint: 'simple', authorization: false }), diff --git a/yarn.lock b/yarn.lock index 3288ed9a2e..329a17a855 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6453,6 +6453,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-proxy-backend@workspace:plugins/proxy-backend" dependencies: + "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" @@ -6472,7 +6473,6 @@ __metadata: morgan: ^1.10.0 msw: ^2.0.0 node-fetch: ^2.6.7 - portfinder: ^1.0.32 uuid: ^9.0.0 winston: ^3.2.1 yaml: ^2.0.0 From 8b53ded4c5ce4de8fc5c27120071ba8bebf93239 Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Thu, 23 May 2024 11:49:53 +0200 Subject: [PATCH 078/118] fix lint Signed-off-by: Alex Eftimie --- plugins/search/src/apis.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/plugins/search/src/apis.test.ts b/plugins/search/src/apis.test.ts index 5d0112c7c7..15745d2e68 100644 --- a/plugins/search/src/apis.test.ts +++ b/plugins/search/src/apis.test.ts @@ -58,19 +58,6 @@ describe('apis', () => { ); }); - // it('Sets Authorization if token is available', async () => { - // identityApi.getCredentials.mockResolvedValue({ token: 'token' }); - // await client.query(query); - // expect(getBaseUrl).toHaveBeenLastCalledWith('search'); - // expect(mockFetch).toHaveBeenLastCalledWith( - // expect.objectContaining({ - // agent: undefined, - // query: 'term=', - // headers: { authorization: ["Bearer token"] } - // }) - // ); - // }); - it('Resolves JSON from fetch response', async () => { const result = { loading: false, error: '', value: {} }; json.mockReturnValueOnce(result); From a9791bcec036045580aad607833d3a47e0bccc35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 23 May 2024 14:32:45 +0200 Subject: [PATCH 079/118] fix for new config shape too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../external/ExternalTokenHandler.test.ts | 156 ++++++++++++++++++ .../auth/external/jwks.test.ts | 98 +++++++---- .../implementations/auth/external/jwks.ts | 50 ++++-- 3 files changed, 258 insertions(+), 46 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts index 1e82f76134..559107c59c 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts @@ -17,8 +17,72 @@ import { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; import { ExternalTokenHandler } from './ExternalTokenHandler'; import { TokenHandler } from './types'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; +import { randomBytes } from 'crypto'; +import { SignJWT, exportJWK, generateKeyPair } from 'jose'; +import { DateTime } from 'luxon'; +import { v4 as uuid } from 'uuid'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; + +// Simplified copy of TokenFactory in @backstage/plugin-auth-backend +interface AnyJWK extends Record { + use: 'sig'; + alg: string; + kid: string; + kty: string; +} +class FakeTokenFactory { + private readonly keys = new Array(); + + constructor( + private readonly options: { + issuer: string; + keyDurationSeconds: number; + }, + ) {} + + async issueToken(params: { + claims: { + sub: string; + ent?: string[]; + }; + }): Promise { + const pair = await generateKeyPair('RS256'); + const publicKey = await exportJWK(pair.publicKey); + const kid = uuid(); + publicKey.kid = kid; + this.keys.push(publicKey as AnyJWK); + + const iss = this.options.issuer; + const sub = params.claims.sub; + const ent = params.claims.ent; + const aud = 'backstage'; + const iat = Math.floor(Date.now() / 1000); + const exp = iat + this.options.keyDurationSeconds; + + return new SignJWT({ iss, sub, aud, iat, exp, ent, kid }) + .setProtectedHeader({ alg: 'RS256', ent: ent, kid: kid }) + .setIssuer(iss) + .setAudience(aud) + .setSubject(sub) + .setIssuedAt(iat) + .setExpirationTime(exp) + .sign(pair.privateKey); + } + + async listPublicKeys(): Promise<{ keys: AnyJWK[] }> { + return { keys: this.keys }; + } +} describe('ExternalTokenHandler', () => { + const server = setupServer(); + setupRequestMockHandlers(server); + it('skips over inner handlers that do not match, and applies plugin restrictions', async () => { const handler1: TokenHandler = { add: jest.fn(), @@ -52,4 +116,96 @@ describe('ExternalTokenHandler', () => { `"This token's access is restricted to plugin(s) 'plugin1'"`, ); }); + + it('successfully parses known methods', async () => { + const legacyKey = randomBytes(24); + + const factory = new FakeTokenFactory({ + issuer: 'blah', + keyDurationSeconds: 100, + }); + + server.use( + rest.get( + 'https://example.com/.well-known/jwks.json', + async (_, res, ctx) => { + const keys = await factory.listPublicKeys(); + return res(ctx.json(keys)); + }, + ), + ); + + const handler = ExternalTokenHandler.create({ + ownPluginId: 'catalog', + logger: mockServices.logger.mock(), + config: mockServices.rootConfig({ + data: { + backend: { + auth: { + externalAccess: [ + { + type: 'legacy', + options: { + secret: legacyKey.toString('base64'), + subject: 'legacy-subject', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + { + type: 'static', + options: { + token: 'defdefdef', + subject: 'static-subject', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + { + type: 'jwks', + options: { + url: 'https://example.com/.well-known/jwks.json', + algorithm: 'RS256', + issuer: 'blah', + audience: 'backstage', + subjectPrefix: 'custom-prefix', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + ], + }, + }, + }, + }), + }); + + const legacyToken = await new SignJWT({ + sub: 'backstage-server', + exp: DateTime.now().plus({ minutes: 1 }).toUnixInteger(), + }) + .setProtectedHeader({ alg: 'HS256' }) + .sign(legacyKey); + + await expect(handler.verifyToken(legacyToken)).resolves.toEqual({ + subject: 'legacy-subject', + accessRestrictions: { permissionNames: ['catalog.entity.read'] }, + }); + + await expect(handler.verifyToken('defdefdef')).resolves.toEqual({ + subject: 'static-subject', + accessRestrictions: { permissionNames: ['catalog.entity.read'] }, + }); + + const jwksToken = await factory.issueToken({ + claims: { sub: 'jwks-subject' }, + }); + await expect(handler.verifyToken(jwksToken)).resolves.toEqual({ + subject: 'external:custom-prefix:jwks-subject', + accessRestrictions: { permissionNames: ['catalog.entity.read'] }, + }); + }); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts index 0466cdf034..56930e4480 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { SignJWT, exportJWK, generateKeyPair } from 'jose'; @@ -21,13 +22,13 @@ import { setupServer } from 'msw/node'; import { v4 as uuid } from 'uuid'; import { JWKSHandler } from './jwks'; +// Simplified copy of TokenFactory in @backstage/plugin-auth-backend interface AnyJWK extends Record { use: 'sig'; alg: string; kid: string; kty: string; } -// Simplified copy of TokenFactory in @backstage/plugin-auth-backend class FakeTokenFactory { private readonly keys = new Array(); @@ -100,10 +101,12 @@ describe('JWKSHandler', () => { it('verifies token with valid entry', async () => { const validEntry = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: mockBaseUrl, - audience: 'backstage', + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: mockBaseUrl, + audience: 'backstage', + }, }; const jwksHandler = new JWKSHandler(); @@ -120,17 +123,21 @@ describe('JWKSHandler', () => { it('skips invalid entry and continues verification', async () => { const invalidEntry = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: ['fakeIssuer'], - audience: ['fakeAud'], + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: ['fakeIssuer'], + audience: ['fakeAud'], + }, }; const validEntry = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: ['multiple-issuers', mockBaseUrl], - audience: ['multiple-audiences', 'backstage'], + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: ['multiple-issuers', mockBaseUrl], + audience: ['multiple-audiences', 'backstage'], + }, }; const jwksHandler = new JWKSHandler(); @@ -148,15 +155,19 @@ describe('JWKSHandler', () => { it('returns undefined if no valid entry found', async () => { const invalidEntry1 = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: 'wrong', + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: 'wrong', + }, }; const invalidEntry2 = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: ['HS256'], - audience: 'wrong', + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: ['HS256'], + audience: 'wrong', + }, }; const jwksHandler = new JWKSHandler(); @@ -178,17 +189,21 @@ describe('JWKSHandler', () => { expect(() => { jwksHandler.add( new ConfigReader({ - url: 'https://exampl e.com/jwks', + options: { + url: 'https://exampl e.com/jwks', + }, }), ); - }).toThrow('Invalid URL'); + }).toThrow('Illegal JWKS URL, must be a set of non-space characters'); expect(() => { jwksHandler.add( new ConfigReader({ - url: 'https://example.com/jwks\n', + options: { + url: 'https://example.com/jwks\n', + }, }), ); - }).toThrow('Illegal URL, must be a set of non-space characters'); + }).toThrow('Illegal JWKS URL, must be a set of non-space characters'); }); it('gracefully handles no added tokens', async () => { @@ -198,11 +213,13 @@ describe('JWKSHandler', () => { it('uses custom subject prefix if provided', async () => { const validEntry = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: mockBaseUrl, - audience: 'backstage', - subjectPrefix: 'custom-prefix', + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: mockBaseUrl, + audience: 'backstage', + subjectPrefix: 'custom-prefix', + }, }; const jwksHandler = new JWKSHandler(); @@ -215,7 +232,30 @@ describe('JWKSHandler', () => { const result = await jwksHandler.verifyToken(token); expect(result).toEqual({ - subject: `external:${validEntry.subjectPrefix}:${mockSubject}`, + subject: `external:${validEntry.options.subjectPrefix}:${mockSubject}`, + }); + }); + + it('carries over access restrictions', async () => { + const jwksHandler = new JWKSHandler(); + jwksHandler.add( + new ConfigReader({ + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + }, + accessRestrictions: [{ plugin: 'scaffolder', permission: 'do.it' }], + }), + ); + + const token = await factory.issueToken({ claims: { sub: mockSubject } }); + + await expect(jwksHandler.verifyToken(token)).resolves.toEqual({ + subject: `external:${mockSubject}`, + allAccessRestrictions: new Map( + Object.entries({ + scaffolder: { permissionNames: ['do.it'] }, + }), + ), }); }); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index 8af44f4d54..d88dc62a47 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -16,8 +16,11 @@ import { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose'; import { Config } from '@backstage/config'; -import { readStringOrStringArrayFromConfig } from './helpers'; -import { TokenHandler } from './types'; +import { + readAccessRestrictionsFromConfig, + readStringOrStringArrayFromConfig, +} from './helpers'; +import { AccessRestriptionsMap, TokenHandler } from './types'; /** * Handles `type: jwks` access. @@ -32,20 +35,30 @@ export class JWKSHandler implements TokenHandler { subjectPrefix?: string; url: URL; jwks: JWTVerifyGetKey; + allAccessRestrictions?: AccessRestriptionsMap; }> = []; - add(options: Config) { - const algorithms = readStringOrStringArrayFromConfig(options, 'algorithm'); - const issuers = readStringOrStringArrayFromConfig(options, 'issuer'); - const audiences = readStringOrStringArrayFromConfig(options, 'audience'); - const subjectPrefix = options.getOptionalString('subjectPrefix'); - const url = new URL(options.getString('url')); - const jwks = createRemoteJWKSet(url); - - if (!options.getString('url').match(/^\S+$/)) { - throw new Error('Illegal URL, must be a set of non-space characters'); + add(config: Config) { + if (!config.getString('options.url').match(/^\S+$/)) { + throw new Error( + 'Illegal JWKS URL, must be a set of non-space characters', + ); } + const algorithms = readStringOrStringArrayFromConfig( + config, + 'options.algorithm', + ); + const issuers = readStringOrStringArrayFromConfig(config, 'options.issuer'); + const audiences = readStringOrStringArrayFromConfig( + config, + 'options.audience', + ); + const subjectPrefix = config.getOptionalString('options.subjectPrefix'); + const url = new URL(config.getString('options.url')); + const jwks = createRemoteJWKSet(url); + const allAccessRestrictions = readAccessRestrictionsFromConfig(config); + this.#entries.push({ algorithms, audiences, @@ -53,6 +66,7 @@ export class JWKSHandler implements TokenHandler { jwks, subjectPrefix, url, + allAccessRestrictions, }); } @@ -68,11 +82,13 @@ export class JWKSHandler implements TokenHandler { }); if (sub) { - if (entry.subjectPrefix) { - return { subject: `external:${entry.subjectPrefix}:${sub}` }; - } - - return { subject: `external:${sub}` }; + const prefix = entry.subjectPrefix + ? `external:${entry.subjectPrefix}:` + : 'external:'; + return { + subject: `${prefix}${sub}`, + allAccessRestrictions: entry.allAccessRestrictions, + }; } } catch { continue; From d7cdf979c8800f8a8037499fe4f3f3f2431aa3cf Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 24 May 2024 11:14:47 +0200 Subject: [PATCH 080/118] Move @backstage/repo-tools to devDependencies Signed-off-by: Marcus --- plugins/search-backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 523abc24c9..40371682fc 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -58,7 +58,6 @@ "@backstage/plugin-permission-node": "workspace:^", "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-search-common": "workspace:^", - "@backstage/repo-tools": "workspace:^", "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "dataloader": "^2.0.0", @@ -72,6 +71,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", + "@backstage/repo-tools": "workspace:^", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, From 34dc47d1943b603470e36126acede8785f7bd780 Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 24 May 2024 11:16:05 +0200 Subject: [PATCH 081/118] Add changeset Signed-off-by: Marcus --- .changeset/shaggy-jokes-promise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shaggy-jokes-promise.md diff --git a/.changeset/shaggy-jokes-promise.md b/.changeset/shaggy-jokes-promise.md new file mode 100644 index 0000000000..e7788b69f4 --- /dev/null +++ b/.changeset/shaggy-jokes-promise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend': patch +--- + +Move @backstage/repo-tools to devDependencies From c00f7ee0f294931139920b656df42df2b86ab246 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 24 May 2024 15:13:48 +0200 Subject: [PATCH 082/118] chore: added changeset Signed-off-by: blam --- .changeset/gold-teachers-wink.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/gold-teachers-wink.md diff --git a/.changeset/gold-teachers-wink.md b/.changeset/gold-teachers-wink.md new file mode 100644 index 0000000000..0578e8d02b --- /dev/null +++ b/.changeset/gold-teachers-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fix issue with `esm` loaded dependencies being different from the `cjs` import for Vite dependencies From eff06359a8299298e632087ccefaf3f0434b5f8d Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Fri, 24 May 2024 17:05:49 +0200 Subject: [PATCH 083/118] feat: add scaffolder action to trigger gitlab pipelines Signed-off-by: ElaineDeMattosSilvaB --- .../actions/gitlabPipelineTrigger.examples.ts | 41 +++++++ .../src/actions/gitlabPipelineTrigger.ts | 102 ++++++++++++++++++ .../src/actions/index.ts | 7 +- .../src/module.ts | 4 +- 4 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts new file mode 100644 index 0000000000..4948db908b --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts @@ -0,0 +1,41 @@ +/* + * 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 { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; +import { commonGitlabConfigExample } from '../commonGitlabConfig'; + +export const examples: TemplateExample[] = [ + { + description: 'Trigger a GitLab Project Pipeline', + example: yaml.stringify({ + steps: [ + { + id: 'triggerPipeline', + name: 'Trigger Project Pipeline', + action: 'gitlab:pipeline:trigger', + input: { + ...commonGitlabConfigExample, + projectId: 12, + tokenDescription: + 'This is the text that will appear in the pipeline token', + token: 'glpt-xxxxxxxxxxxx', + branch: 'main', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts new file mode 100644 index 0000000000..2edb529ea7 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts @@ -0,0 +1,102 @@ +/* + * 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 { InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + ExpandedPipelineSchema, + PipelineTriggerTokenSchema, +} from '@gitbeaker/rest'; +import { z } from 'zod'; +import commonGitlabConfig from '../commonGitlabConfig'; +import { getClient, parseRepoUrl } from '../util'; +import { examples } from './gitlabPipelineTrigger.examples'; + +const pipelineInputProperties = z.object({ + projectId: z.number().describe('Project Id'), + tokenDescription: z.string().describe('Pipeline token description'), + branch: z.string().describe('Project branch'), +}); + +const pipelineOutputProperties = z.object({ + pipelineUrl: z.string({ description: 'Pipeline Url' }), +}); + +/** + * Creates a `gitlab:pipeline:trigger` Scaffolder action. + * + * @param options - Templating configuration. + * @public + */ +export const createTriggerGitlabPipelineAction = (options: { + integrations: ScmIntegrationRegistry; +}) => { + const { integrations } = options; + return createTemplateAction({ + id: 'gitlab:pipeline:trigger', + description: 'Triggers a GitLab Pipeline.', + examples, + schema: { + input: commonGitlabConfig.merge(pipelineInputProperties), + output: pipelineOutputProperties, + }, + async handler(ctx) { + try { + const { repoUrl, projectId, tokenDescription, token, branch } = + commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input); + + const { host } = parseRepoUrl(repoUrl, integrations); + const api = getClient({ host, integrations, token }); + + // Get a pipeline token + const createdPipelineTokenResponse = + (await api.PipelineTriggerTokens.create( + projectId, + tokenDescription, + )) as PipelineTriggerTokenSchema; + + if (!createdPipelineTokenResponse.token) { + return; + } + // Use the pipeline token to trigger the pipeline in the project + const pipelineTriggerResponse = + (await api.PipelineTriggerTokens.trigger( + projectId, + branch, + createdPipelineTokenResponse.token, + )) as ExpandedPipelineSchema; + + // Delete the pipeline token + await api.PipelineTriggerTokens.remove( + projectId, + createdPipelineTokenResponse.id, + ); + + ctx.output('pipelineUrl', pipelineTriggerResponse.web_url); + } catch (error: any) { + if (error instanceof z.ZodError) { + // Handling Zod validation errors + throw new InputError(`Validation error: ${error.message}`, { + validationErrors: error.errors, + }); + } + // Handling other errors + throw new InputError(`Failed to trigger Pipeline: ${error.message}`); + } + }, + }); +}; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts index 4a5ca06c98..fb80a43ff4 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts @@ -14,10 +14,11 @@ * limitations under the License. */ export * from './createGitlabGroupEnsureExistsAction'; -export * from './createGitlabProjectDeployTokenAction'; -export * from './createGitlabProjectAccessTokenAction'; -export * from './createGitlabProjectVariableAction'; export * from './createGitlabIssueAction'; +export * from './createGitlabProjectAccessTokenAction'; +export * from './createGitlabProjectDeployTokenAction'; +export * from './createGitlabProjectVariableAction'; export * from './gitlab'; export * from './gitlabMergeRequest'; export * from './gitlabRepoPush'; +export * from './gitlabPipelineTrigger'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/module.ts b/plugins/scaffolder-backend-module-gitlab/src/module.ts index 3571575827..17052b987f 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/module.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/module.ts @@ -17,6 +17,7 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; +import { ScmIntegrations } from '@backstage/integration'; import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; import { createGitlabGroupEnsureExistsAction, @@ -27,8 +28,8 @@ import { createGitlabRepoPushAction, createPublishGitlabAction, createPublishGitlabMergeRequestAction, + createTriggerGitlabPipelineAction, } from './actions'; -import { ScmIntegrations } from '@backstage/integration'; /** * @public @@ -55,6 +56,7 @@ export const gitlabModule = createBackendModule({ createGitlabRepoPushAction({ integrations }), createPublishGitlabAction({ config, integrations }), createPublishGitlabMergeRequestAction({ integrations }), + createTriggerGitlabPipelineAction({ integrations }), ); }, }); From 788eca7addd3375c2adb60327fe87382e550cdcf Mon Sep 17 00:00:00 2001 From: Stephen Glass Date: Fri, 24 May 2024 23:42:14 -0400 Subject: [PATCH 084/118] fix readme for new plugins created using cli Signed-off-by: Stephen Glass --- .changeset/eighty-yaks-switch.md | 5 +++++ packages/cli/templates/default-backend-plugin/README.md.hbs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/eighty-yaks-switch.md diff --git a/.changeset/eighty-yaks-switch.md b/.changeset/eighty-yaks-switch.md new file mode 100644 index 0000000000..30e3374d70 --- /dev/null +++ b/.changeset/eighty-yaks-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fix readme for new plugins created using cli diff --git a/packages/cli/templates/default-backend-plugin/README.md.hbs b/packages/cli/templates/default-backend-plugin/README.md.hbs index e95c626167..366ed27104 100644 --- a/packages/cli/templates/default-backend-plugin/README.md.hbs +++ b/packages/cli/templates/default-backend-plugin/README.md.hbs @@ -7,7 +7,7 @@ _This plugin was created through the Backstage CLI_ ## Getting started Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn -start` in the root directory, and then navigating to [/{{pluginVar}}/health](http://localhost:7007/api/{{pluginVar}}/health). +start` in the root directory, and then navigating to [/{{id}}/health](http://localhost:7007/api/{{id}}/health). You can also serve the plugin in isolation by running `yarn start` in the plugin directory. This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. From 1354d81b86f74d9c173dee15b96a3cc5f46cc8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 25 May 2024 11:38:55 +0200 Subject: [PATCH 085/118] use node-fetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/nice-pants-shave.md | 8 +++ .../src/helpers.ts | 1 + plugins/notifications-node/package.json | 1 + .../DefaultNotificationService.test.ts | 58 +++++++++---------- .../src/service/DefaultNotificationService.ts | 3 +- .../src/actions/gitea.ts | 1 + .../package.json | 1 + .../src/actions/createProject.ts | 1 + yarn.lock | 2 + 9 files changed, 44 insertions(+), 32 deletions(-) create mode 100644 .changeset/nice-pants-shave.md diff --git a/.changeset/nice-pants-shave.md b/.changeset/nice-pants-shave.md new file mode 100644 index 0000000000..500888a817 --- /dev/null +++ b/.changeset/nice-pants-shave.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch +'@backstage/plugin-scaffolder-backend-module-sentry': patch +'@backstage/plugin-scaffolder-backend-module-gitea': patch +'@backstage/plugin-notifications-node': patch +--- + +Use `node-fetch` instead of native fetch, as per https://backstage.io/docs/architecture-decisions/adrs-adr013 diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts index 60411e53d5..c1a14ae6b4 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts @@ -23,6 +23,7 @@ import { } from '@backstage/errors'; import express from 'express'; import { createRemoteJWKSet, jwtVerify } from 'jose'; +import fetch, { Headers } from 'node-fetch'; import { CACHE_PREFIX, CF_JWT_HEADER, diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 1f3760f5c6..2500026f3b 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -37,6 +37,7 @@ "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-signals-node": "workspace:^", "knex": "^3.0.0", + "node-fetch": "^2.6.7", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/plugins/notifications-node/src/service/DefaultNotificationService.test.ts b/plugins/notifications-node/src/service/DefaultNotificationService.test.ts index 4711f3de18..d5c327e788 100644 --- a/plugins/notifications-node/src/service/DefaultNotificationService.test.ts +++ b/plugins/notifications-node/src/service/DefaultNotificationService.test.ts @@ -24,8 +24,6 @@ import { } from './DefaultNotificationService'; import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; -const server = setupServer(); - const testNotification: NotificationPayload = { title: 'Notification 1', link: '/catalog', @@ -33,8 +31,12 @@ const testNotification: NotificationPayload = { }; describe('DefaultNotificationService', () => { + const server = setupServer(); setupRequestMockHandlers(server); - const discovery = mockServices.discovery(); + + const discovery = mockServices.discovery.mock({ + getBaseUrl: jest.fn().mockResolvedValue('http://example.com'), + }); const auth = mockServices.auth(); let service: DefaultNotificationService; @@ -53,20 +55,17 @@ describe('DefaultNotificationService', () => { }; server.use( - rest.post( - `${await discovery.getBaseUrl('notifications')}/`, - async (req, res, ctx) => { - const json = await req.json(); - expect(json).toEqual(body); - expect(req.headers.get('Authorization')).toBe( - mockCredentials.service.header({ - onBehalfOf: await auth.getOwnServiceCredentials(), - targetPluginId: 'notifications', - }), - ); - return res(ctx.status(200)); - }, - ), + rest.post('http://example.com', async (req, res, ctx) => { + const json = await req.json(); + expect(json).toEqual(body); + expect(req.headers.get('Authorization')).toBe( + mockCredentials.service.header({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'notifications', + }), + ); + return res(ctx.status(200)); + }), ); await expect(service.send(body)).resolves.toBeUndefined(); }); @@ -78,20 +77,17 @@ describe('DefaultNotificationService', () => { }; server.use( - rest.post( - `${await discovery.getBaseUrl('notifications')}/`, - async (req, res, ctx) => { - const json = await req.json(); - expect(json).toEqual(body); - expect(req.headers.get('Authorization')).toBe( - mockCredentials.service.header({ - onBehalfOf: await auth.getOwnServiceCredentials(), - targetPluginId: 'notifications', - }), - ); - return res(ctx.status(400)); - }, - ), + rest.post('http://example.com', async (req, res, ctx) => { + const json = await req.json(); + expect(json).toEqual(body); + expect(req.headers.get('Authorization')).toBe( + mockCredentials.service.header({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'notifications', + }), + ); + return res(ctx.status(400)); + }), ); await expect(service.send(body)).rejects.toThrow( 'Request failed with status 400', diff --git a/plugins/notifications-node/src/service/DefaultNotificationService.ts b/plugins/notifications-node/src/service/DefaultNotificationService.ts index 950f42ae83..7a46e01689 100644 --- a/plugins/notifications-node/src/service/DefaultNotificationService.ts +++ b/plugins/notifications-node/src/service/DefaultNotificationService.ts @@ -17,6 +17,7 @@ import { NotificationService } from './NotificationService'; import { AuthService, DiscoveryService } from '@backstage/backend-plugin-api'; import { NotificationPayload } from '@backstage/plugin-notifications-common'; +import fetch from 'node-fetch'; /** @public */ export type NotificationServiceOptions = { @@ -68,7 +69,7 @@ export class DefaultNotificationService implements NotificationService { targetPluginId: 'notifications', }); - const response = await fetch(`${baseUrl}/`, { + const response = await fetch(baseUrl, { method: 'POST', body: JSON.stringify(notification), headers: { diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts index 6b29ff2a4c..601a670237 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts @@ -30,6 +30,7 @@ import { } from '@backstage/plugin-scaffolder-node'; import { examples } from './gitea.examples'; import crypto from 'crypto'; +import fetch, { Response, RequestInit } from 'node-fetch'; const checkGiteaContentUrl = async ( config: GiteaIntegrationConfig, diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index a44de6705a..861e78b565 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -44,6 +44,7 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", + "node-fetch": "^2.6.7", "yaml": "^2.3.3" }, "devDependencies": { diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts index 259b670f9e..686053b178 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts @@ -17,6 +17,7 @@ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { InputError } from '@backstage/errors'; import { Config } from '@backstage/config'; +import fetch from 'node-fetch'; /** * Creates the `sentry:project:create` Scaffolder action. diff --git a/yarn.lock b/yarn.lock index fde9aa7d1f..214d43ecfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6259,6 +6259,7 @@ __metadata: "@backstage/test-utils": "workspace:^" knex: ^3.0.0 msw: ^1.0.0 + node-fetch: ^2.6.7 uuid: ^9.0.0 languageName: unknown linkType: soft @@ -6751,6 +6752,7 @@ __metadata: "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/types": "workspace:^" msw: ^2.0.0 + node-fetch: ^2.6.7 yaml: ^2.3.3 languageName: unknown linkType: soft From debcc8c8d375605cad53116cf4242f82ef059c23 Mon Sep 17 00:00:00 2001 From: David Weber Date: Sun, 26 May 2024 21:06:29 +0200 Subject: [PATCH 086/118] feat: migrate LDAP catalog module to the new backend system Signed-off-by: David Weber --- .changeset/spotty-plants-switch.md | 5 + .../building-backends/08-migrating.md | 2 +- docs/integrations/ldap/org--old.md | 417 ++++++++++++++++++ docs/integrations/ldap/org.md | 223 +++------- plugins/catalog-backend-module-ldap/README.md | 4 + .../catalog-backend-module-ldap/api-report.md | 52 ++- .../catalog-backend-module-ldap/config.d.ts | 231 +++++++++- .../catalog-backend-module-ldap/src/index.ts | 5 + .../src/ldap/config.test.ts | 288 ++++++------ .../src/ldap/config.ts | 338 ++++++++------ .../src/ldap/index.ts | 2 +- .../catalog-backend-module-ldap/src/module.ts | 114 +++++ .../src/processors/LdapOrgEntityProvider.ts | 123 +++++- .../src/processors/LdapOrgReaderProcessor.ts | 4 +- .../src/processors/index.ts | 5 +- 15 files changed, 1358 insertions(+), 455 deletions(-) create mode 100644 .changeset/spotty-plants-switch.md create mode 100644 docs/integrations/ldap/org--old.md create mode 100644 plugins/catalog-backend-module-ldap/src/module.ts diff --git a/.changeset/spotty-plants-switch.md b/.changeset/spotty-plants-switch.md new file mode 100644 index 0000000000..a5951de05d --- /dev/null +++ b/.changeset/spotty-plants-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': minor +--- + +Migrate LDAP catalog module to the new backend system. diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md index ae41193b11..d38acb695e 100644 --- a/docs/backend-system/building-backends/08-migrating.md +++ b/docs/backend-system/building-backends/08-migrating.md @@ -1369,7 +1369,7 @@ The vast majority of the backend plugins that currently live in the Backstage Re | @backstage/plugin-catalog-backend-module-github-org | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-github-org/README.md) | | @backstage/plugin-catalog-backend-module-gitlab | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-gitlab/README.md) | | @backstage/plugin-catalog-backend-module-incremental-ingestion | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-incremental-ingestion/README.md) | -| @backstage/plugin-catalog-backend-module-ldap | backend-plugin-module | | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md) | +| @backstage/plugin-catalog-backend-module-ldap | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md) | | @backstage/plugin-catalog-backend-module-msgraph | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md) | | @backstage/plugin-catalog-backend-module-openapi | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-openapi/README.md) | | @backstage/plugin-catalog-backend-module-puppetdb | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-puppetdb/README.md) | diff --git a/docs/integrations/ldap/org--old.md b/docs/integrations/ldap/org--old.md new file mode 100644 index 0000000000..13fbcb9408 --- /dev/null +++ b/docs/integrations/ldap/org--old.md @@ -0,0 +1,417 @@ +--- +id: org--old +title: LDAP Organizational Data +sidebar_label: Org Data +# prettier-ignore +description: Setting up ingestion of organizational data from LDAP +--- + +The Backstage catalog can be set up to ingest organizational data - users and +groups - directly from an LDAP compatible service. The result is a hierarchy of +[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and +[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind +entities that mirror your org setup. + +## Supported vendors + +Backstage in general supports OpenLDAP compatible vendors, as well as Active Directory and FreeIPA. If you are using a vendor that does not seem to be supported, please [file an issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md). + +## Installation + +This guide will use the Entity Provider method. If you for some reason prefer +the Processor method (not recommended), it is described separately below. + +The provider is not installed by default, therefore you have to add a dependency +to `@backstage/plugin-catalog-backend-module-ldap` to your backend package. + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap +``` + +:::note Note + +When configuring to use a Provider instead of a Processor you do not +need to add a _location_ pointing to your LDAP server + +::: + +Update the catalog plugin initialization in your backend to add the provider and +schedule it: + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + + /* highlight-add-start */ + // The target parameter below needs to match the ldap.providers.target + // value specified in your app-config. + builder.addEntityProvider( + LdapOrgEntityProvider.fromConfig(env.config, { + id: 'our-ldap-master', + target: 'ldaps://ds.example.net', + logger: env.logger, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 60 }, + timeout: { minutes: 15 }, + }), + }), + ); + /* highlight-add-end */ + + // .. +} +``` + +After this, you also have to add some configuration in your app-config that +describes what you want to import for that target. + +## Configuration + +The following configuration is a small example of how a setup could look for +importing groups and users from a corporate LDAP server. + +```yaml +ldap: + providers: + - target: ldaps://ds.example.net + bind: + dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net + secret: ${LDAP_SECRET} + users: + dn: ou=people,ou=example,dc=example,dc=net + options: + filter: (uid=*) + map: + description: l + set: + metadata.customField: 'hello' + groups: + dn: ou=access,ou=groups,ou=example,dc=example,dc=net + options: + filter: (&(objectClass=some-group-class)(!(groupType=email))) + map: + description: l + set: + metadata.customField: 'hello' +``` + +There may be many providers, each targeting a specific `target` which is +supposed to match the `target` of a dedicated provider instance - i.e., you will +add one entity provider class instance per target to ingest from. + +These config blocks have a lot of options in them, so we will describe each +"root" key within the block separately. + +### target + +This is the URL of the targeted server, typically on the form +`ldaps://ds.example.net` for SSL enabled servers or `ldap://ds.example.net` +without SSL. + +#### target.tls.keys + +`keys` in TLS options specifies location of a file, that contains private keys +to establish connection with your LDAP server, in PEM format. See an example +for Google Secure LDAP Service below. + +#### target.tls.certs + +`certs` in TLS options specifies location of a file, that contains certificate +chains to establish connection with your LDAP server, in PEM format. See an +example for Google Secure LDAP Service below. + +### bind + +The bind block specifies how the plugin should bind (essentially, to +authenticate) towards the server. It has the following fields. + +```yaml +dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net +secret: ${LDAP_SECRET} +``` + +The `dn` is the full LDAP Distinguished Name for the user that the plugin +authenticates itself as. At this point, only regular user based authentication +is supported. + +The `secret` is the password of the same user. In this example, it is given in +the form of an environment variable `LDAP_SECRET`, that has to be set when the +backend starts. + +### users + +The `users` block defines the settings that govern the reading and +interpretation of users. Its fields are explained in separate sections below. + +#### users.dn + +The DN under which users are stored, e.g. +`ou=people,ou=example,dc=example,dc=net`. + +#### users.options + +The search options to use when sending the query to the server, when reading all +users. All the options are shown below, with their default values, but they are +all optional. + +```yaml +options: + # One of 'base', 'one', or 'sub'. + scope: one + # The filter is the one that you commonly will want to specify explicitly. It + # is a string on the standard LDAP query format. Use it to select out the set + # of users that are of actual interest to ingest. For example, you may want + # to filter out disabled users. + filter: (uid=*) + # The attribute selectors for each item, as passed to the LDAP server. + attributes: ['*', '+'] + # This field is either 'false' to disable paging when reading from the + # server, or an object on the form '{ pageSize: 100, pagePause: true }' that + # specifies the details of how the paging shall work. + paged: false +``` + +#### users.set + +This optional piece lets you specify a number of JSON paths (on a.b.c form) and +hard coded values to set on those paths. This can be useful for example if you +want to hard code a namespace or similar on the generated entities. + +```yaml +set: + # Just an example; the key and value can be anything + metadata.namespace: 'ldap' +``` + +#### users.map + +Mappings from well known entity fields, to LDAP attribute names. This is where +you are able to define how to interpret the attributes of each LDAP result item, +and to move them into the corresponding entity fields. All the options are shown +below, with their default values, but they are all optional. + +If you leave out an optional mapping, it will still be copied using that default +value. For example, even if you do not put in the field `displayName` in your +config, the provider will still copy the attribute `cn` into the entity field +`spec.profile.displayName`. + +```yaml +map: + # The name of the attribute that holds the relative + # distinguished name of each entry. + rdn: uid + # The name of the attribute that shall be used for the value of + # the metadata.name field of the entity. + name: uid + # The name of the attribute that shall be used for the value of + # the metadata.description field of the entity. + description: description + # The name of the attribute that shall be used for the value of + # the spec.profile.displayName field of the entity. + displayName: cn + # The name of the attribute that shall be used for the value of + # the spec.profile.email field of the entity. + email: mail + # The name of the attribute that shall be used for the value of + # the spec.profile.picture field of the entity. + picture: + # The name of the attribute that shall be used for the values of + # the spec.memberOf field of the entity. + memberOf: memberOf +``` + +### groups + +The `groups` block defines the settings that govern the reading and +interpretation of groups. Its fields are explained in separate sections below. + +#### groups.dn + +The DN under which groups are stored, e.g. +`ou=people,ou=example,dc=example,dc=net`. + +#### groups.options + +The search options to use when sending the query to the server, when reading all +groups. All the options are shown below, with their default values, but they are +all optional. + +```yaml +options: + # One of 'base', 'one', or 'sub'. + scope: one + # The filter is the one that you commonly will want to specify explicitly. It + # is a string on the standard LDAP query format. Use it to select out the set + # of groups that are of actual interest to ingest. For example, you may want + # to filter out disabled groups. + filter: (&(objectClass=some-group-class)(!(groupType=email))) + # The attribute selectors for each item, as passed to the LDAP server. + attributes: ['*', '+'] + # This field is either 'false' to disable paging when reading from the + # server, or an object on the form '{ pageSize: 100, pagePause: true }' that + # specifies the details of how the paging shall work. + paged: false +``` + +#### groups.set + +This optional piece lets you specify a number of JSON paths (on a.b.c form) and +hard coded values to set on those paths. This can be useful for example if you +want to hard code a namespace or similar on the generated entities. + +```yaml +set: + # Just an example; the key and value can be anything + metadata.namespace: 'ldap' +``` + +#### groups.map + +Mappings from well known entity fields, to LDAP attribute names. This is where +you are able to define how to interpret the attributes of each LDAP result item, +and to move them into the corresponding entity fields. All of the options are +shown below, with their default values, but they are all optional. + +If you leave out an optional mapping, it will still be copied using that default +value. For example, even if you do not put in the field `displayName` in your +config, the provider will still copy the attribute `cn` into the entity field +`spec.profile.displayName`. If the target field is optional, such as the display +name, the importer will accept missing attributes and just leave the target +field unset. If the target field is mandatory, such as the name of the entity, +validation will fail if the source attribute is missing. + +```yaml +map: + # The name of the attribute that holds the relative + # distinguished name of each entry. This value is copied into a + # well known annotation to be able to query by it later. + rdn: cn + # The name of the attribute that shall be used for the value of + # the metadata.name field of the entity. + name: cn + # The name of the attribute that shall be used for the value of + # the metadata.description field of the entity. + description: description + # The name of the attribute that shall be used for the value of + # the spec.type field of the entity. + type: groupType + # The name of the attribute that shall be used for the value of + # the spec.profile.displayName field of the entity. + displayName: cn + # The name of the attribute that shall be used for the value of + # the spec.profile.email field of the entity. + email: + # The name of the attribute that shall be used for the value of + # the spec.profile.picture field of the entity. + picture: + # The name of the attribute that shall be used for the values of + # the spec.parent field of the entity. + memberOf: memberOf + # The name of the attribute that shall be used for the values of + # the spec.children field of the entity. + members: member +``` + +## Customize the Provider + +In case you want to customize the ingested entities, the provider allows to pass +transformers for users and groups. Here we will show an example of overriding +the group transformer. + +1. Create a transformer: + + ```ts + export async function myGroupTransformer( + vendor: LdapVendor, + config: GroupConfig, + group: SearchEntry, + ): Promise { + // Transformations may change namespace, change entity naming pattern, fill + // profile with more or other details... + + // Create the group entity on your own, or wrap the default transformer + return await defaultGroupTransformer(vendor, config, group); + } + ``` + +2. Configure the provider with the transformer: + + ```ts + const ldapEntityProvider = LdapOrgEntityProvider.fromConfig(env.config, { + id: 'our-ldap-master', + target: 'ldaps://ds.example.net', + logger: env.logger, + groupTransformer: myGroupTransformer, + }); + ``` + +## Using a Processor instead of a Provider + +An alternative to using the Provider for ingesting LDAP entries is to use a +Processor. This is the old way that's based on registering locations with the +proper type and target, triggering the processor to run. + +The drawback of this method is that it will leave orphaned Group/User entities +whenever they are deleted on your LDAP server, and you cannot control the +frequency with which they are refreshed, separately from other processors. + +### Processor Installation + +The `LdapOrgReaderProcessor` is not registered by default, so you have to +register it in the catalog plugin: + +```typescript title="packages/backend/src/plugins/catalog.ts" +builder.addProcessor( + LdapOrgReaderProcessor.fromConfig(env.config, { + logger: env.logger, + }), +); +``` + +### Driving LDAP Org Processor Ingestion with Locations + +Locations point out the specific org(s) you want to import. The `type` of these +locations must be `ldap-org`, and the `target` must point to the exact URL +(starting with `ldap://` or `ldaps://`) of the targeted LDAP server. You can +have several such location entries if you want, but typically you will have just +one. + +```yaml +catalog: + locations: + - type: ldap-org + target: ldaps://ds.example.net + rules: + - allow: [User, Group] +``` + +### Example configurations + +#### Google Secure LDAP Service + +To sync Google Workspace/Cloud Identity organization data to users and groups in backstage, +you must [configure Secure LDAP Service](https://support.google.com/a/answer/9048516) first. + +Once Secure LDAP Service is configured, you can enable TLS options in LDAP configuration, +as mentioned below. `keys` and `certs` specify the location of files that are generated +while configuring Secure LDAP Service above. + +```yaml +ldap: + providers: + - target: ldaps://ldap.google.com:636 + tls: + rejectUnauthorized: false + keys: '/var/secrets/tls/gldap.key' + certs: '/var/secrets/tls/gldap.crt' + users: + # users configuration comes here + groups: + # groups configuration comes here +``` diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index a10bc3918c..f70d02afed 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -18,9 +18,6 @@ Backstage in general supports OpenLDAP compatible vendors, as well as Active Dir ## Installation -This guide will use the Entity Provider method. If you for some reason prefer -the Processor method (not recommended), it is described separately below. - The provider is not installed by default, therefore you have to add a dependency to `@backstage/plugin-catalog-backend-module-ldap` to your backend package. @@ -29,47 +26,30 @@ to `@backstage/plugin-catalog-backend-module-ldap` to your backend package. yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap ``` -:::note Note +Next add the basic configuration to `app-config.yaml` -When configuring to use a Provider instead of a Processor you do not -need to add a _location_ pointing to your LDAP server - -::: - -Update the catalog plugin initialization in your backend to add the provider and -schedule it: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The target parameter below needs to match the ldap.providers.target - // value specified in your app-config. - builder.addEntityProvider( - LdapOrgEntityProvider.fromConfig(env.config, { - id: 'our-ldap-master', - target: 'ldaps://ds.example.net', - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} +```yaml title="app-config.yaml" +catalog: + providers: + ldapOrg: + default: + target: ldaps://ds.example.net + bind: + dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net + secret: ${LDAP_SECRET} + schedule: + frequency: PT1H + timeout: PT15M ``` -After this, you also have to add some configuration in your app-config that -describes what you want to import for that target. +Finally, updated your backend by adding the following line: + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-ldap')); +/* highlight-add-end */ +``` ## Configuration @@ -77,34 +57,32 @@ The following configuration is a small example of how a setup could look for importing groups and users from a corporate LDAP server. ```yaml -ldap: +catalog: providers: - - target: ldaps://ds.example.net - bind: - dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net - secret: ${LDAP_SECRET} - users: - dn: ou=people,ou=example,dc=example,dc=net - options: - filter: (uid=*) - map: - description: l - set: - metadata.customField: 'hello' - groups: - dn: ou=access,ou=groups,ou=example,dc=example,dc=net - options: - filter: (&(objectClass=some-group-class)(!(groupType=email))) - map: - description: l - set: - metadata.customField: 'hello' + ldapOrg: + default: + target: ldaps://ds.example.net + bind: + dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net + secret: ${LDAP_SECRET} + users: + dn: ou=people,ou=example,dc=example,dc=net + options: + filter: (uid=*) + map: + description: l + set: + metadata.customField: 'hello' + groups: + dn: ou=access,ou=groups,ou=example,dc=example,dc=net + options: + filter: (&(objectClass=some-group-class)(!(groupType=email))) + map: + description: l + set: + metadata.customField: 'hello' ``` -There may be many providers, each targeting a specific `target` which is -supposed to match the `target` of a dedicated provider instance - i.e., you will -add one entity provider class instance per target to ingest from. - These config blocks have a lot of options in them, so we will describe each "root" key within the block separately. @@ -321,97 +299,34 @@ map: ## Customize the Provider In case you want to customize the ingested entities, the provider allows to pass -transformers for users and groups. Here we will show an example of overriding -the group transformer. +transformers for users and groups. -1. Create a transformer: +Transformers can be configured by extending `ldapOrgEntityProviderTransformExtensionPoint`. Here is an example: - ```ts - export async function myGroupTransformer( - vendor: LdapVendor, - config: GroupConfig, - group: SearchEntry, - ): Promise { - // Transformations may change namespace, change entity naming pattern, fill - // profile with more or other details... +```ts title="packages/backend/src/index.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { ldapOrgEntityProviderTransformExtensionPoint } from '@backstage/plugin-catalog-backend-module-ldap'; +import { myUserTransformer, myGroupTransformer } from './transformers'; - // Create the group entity on your own, or wrap the default transformer - return await defaultGroupTransformer(vendor, config, group); - } - ``` - -2. Configure the provider with the transformer: - - ```ts - const ldapEntityProvider = LdapOrgEntityProvider.fromConfig(env.config, { - id: 'our-ldap-master', - target: 'ldaps://ds.example.net', - logger: env.logger, - groupTransformer: myGroupTransformer, - }); - ``` - -## Using a Processor instead of a Provider - -An alternative to using the Provider for ingesting LDAP entries is to use a -Processor. This is the old way that's based on registering locations with the -proper type and target, triggering the processor to run. - -The drawback of this method is that it will leave orphaned Group/User entities -whenever they are deleted on your LDAP server, and you cannot control the -frequency with which they are refreshed, separately from other processors. - -### Processor Installation - -The `LdapOrgReaderProcessor` is not registered by default, so you have to -register it in the catalog plugin: - -```typescript title="packages/backend/src/plugins/catalog.ts" -builder.addProcessor( - LdapOrgReaderProcessor.fromConfig(env.config, { - logger: env.logger, +backend.add( + createBackendModule({ + pluginId: 'catalog', + moduleId: 'ldap-extensions', + register(env) { + env.registerInit({ + deps: { + /* highlight-add-start */ + ldapTransformers: ldapOrgEntityProviderTransformExtensionPoint, + /* highlight-add-end */ + }, + async init({ ldapTransformers }) { + /* highlight-add-start */ + ldapTransformers.setUserTransformer(myUserTransformer); + ldapTransformers.setGroupTransformer(myGroupTransformer); + /* highlight-add-end */ + }, + }); + }, }), ); ``` - -### Driving LDAP Org Processor Ingestion with Locations - -Locations point out the specific org(s) you want to import. The `type` of these -locations must be `ldap-org`, and the `target` must point to the exact URL -(starting with `ldap://` or `ldaps://`) of the targeted LDAP server. You can -have several such location entries if you want, but typically you will have just -one. - -```yaml -catalog: - locations: - - type: ldap-org - target: ldaps://ds.example.net - rules: - - allow: [User, Group] -``` - -### Example configurations - -#### Google Secure LDAP Service - -To sync Google Workspace/Cloud Identity organization data to users and groups in backstage, -you must [configure Secure LDAP Service](https://support.google.com/a/answer/9048516) first. - -Once Secure LDAP Service is configured, you can enable TLS options in LDAP configuration, -as mentioned below. `keys` and `certs` specify the location of files that are generated -while configuring Secure LDAP Service above. - -```yaml -ldap: - providers: - - target: ldaps://ldap.google.com:636 - tls: - rejectUnauthorized: false - keys: '/var/secrets/tls/gldap.key' - certs: '/var/secrets/tls/gldap.crt' - users: - # users configuration comes here - groups: - # groups configuration comes here -``` diff --git a/plugins/catalog-backend-module-ldap/README.md b/plugins/catalog-backend-module-ldap/README.md index 2bc34ba949..6fd05dda3b 100644 --- a/plugins/catalog-backend-module-ldap/README.md +++ b/plugins/catalog-backend-module-ldap/README.md @@ -8,3 +8,7 @@ groups from your Active Directory or another LDAP compatible server. See [Backstage documentation](https://backstage.io/docs/integrations/ldap/org) for details on how to install and configure the plugin. + +## Legacy backend + +You can find the legacy documentation at `docs/integrations/ldap/org--old.md`. diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md index 76276c8fad..5fcbbc3091 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -3,20 +3,26 @@ > 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 { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node'; import { Client } from 'ldapjs'; import { Config } from '@backstage/config'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; +import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { GroupEntity } from '@backstage/catalog-model'; +import { GroupTransformer as GroupTransformer_2 } from '@backstage/plugin-catalog-backend-module-ldap'; import { JsonValue } from '@backstage/types'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { SearchEntry } from 'ldapjs'; import { SearchOptions } from 'ldapjs'; import { TaskRunner } from '@backstage/backend-tasks'; +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; import { UserEntity } from '@backstage/catalog-model'; +import { UserTransformer as UserTransformer_2 } from '@backstage/plugin-catalog-backend-module-ldap'; // @public export type BindConfig = { @@ -24,6 +30,10 @@ export type BindConfig = { secret: string; }; +// @public +const catalogModuleLdapOrgEntityProvider: () => BackendFeature; +export default catalogModuleLdapOrgEntityProvider; + // @public export function defaultGroupTransformer( vendor: LdapVendor, @@ -109,14 +119,19 @@ export class LdapOrgEntityProvider implements EntityProvider { static fromConfig( configRoot: Config, options: LdapOrgEntityProviderOptions, + ): LdapOrgEntityProvider[]; + // (undocumented) + static fromLegacyConfig( + configRoot: Config, + options: LdapOrgEntityProviderLegacyOptions, ): LdapOrgEntityProvider; // (undocumented) getProviderName(): string; read(options?: { logger?: LoggerService }): Promise; } -// @public -export interface LdapOrgEntityProviderOptions { +// @public @deprecated +export interface LdapOrgEntityProviderLegacyOptions { groupTransformer?: GroupTransformer; id: string; logger: LoggerService; @@ -125,6 +140,30 @@ export interface LdapOrgEntityProviderOptions { userTransformer?: UserTransformer; } +// @public +export type LdapOrgEntityProviderOptions = + | LdapOrgEntityProviderLegacyOptions + | { + logger: LoggerService; + schedule?: 'manual' | TaskRunner; + scheduler?: PluginTaskScheduler; + userTransformer?: UserTransformer | Record; + groupTransformer?: GroupTransformer | Record; + }; + +// @public +export interface LdapOrgEntityProviderTransformsExtensionPoint { + setGroupTransformer( + transformer: GroupTransformer_2 | Record, + ): void; + setUserTransformer( + transformer: UserTransformer_2 | Record, + ): void; +} + +// @public +export const ldapOrgEntityProviderTransformsExtensionPoint: ExtensionPoint; + // @public export class LdapOrgReaderProcessor implements CatalogProcessor { constructor(options: { @@ -154,11 +193,13 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { // @public export type LdapProviderConfig = { + id: string; target: string; tls?: TLSConfig; bind?: BindConfig; users: UserConfig; groups: GroupConfig; + schedule?: TaskScheduleDefinition; }; // @public @@ -176,8 +217,8 @@ export function mapStringAttr( setter: (value: string) => void, ): void; -// @public -export function readLdapConfig(config: Config): LdapProviderConfig[]; +// @public @deprecated +export function readLdapLegacyConfig(config: Config): LdapProviderConfig[]; // @public export function readLdapOrg( @@ -194,6 +235,9 @@ export function readLdapOrg( groups: GroupEntity[]; }>; +// @public +export function readProviderConfigs(config: Config): LdapProviderConfig[]; + // @public export type TLSConfig = { rejectUnauthorized?: boolean; diff --git a/plugins/catalog-backend-module-ldap/config.d.ts b/plugins/catalog-backend-module-ldap/config.d.ts index eb9564f20d..a585471033 100644 --- a/plugins/catalog-backend-module-ldap/config.d.ts +++ b/plugins/catalog-backend-module-ldap/config.d.ts @@ -19,6 +19,8 @@ import { JsonValue } from '@backstage/types'; export interface Config { /** * LdapOrgEntityProvider / LdapOrgReaderProcessor configuration + * + * @deprecated This exists for backwards compatibility only and will be removed in the future. */ ldap?: { /** @@ -240,12 +242,237 @@ export interface Config { /** * Configuration options for the catalog plugin. - * - * TODO(freben): Deprecate this entire block */ catalog?: { + /** + * List of provider-specific options and attributes + */ + providers?: { + /** + * LdapOrg provider key + */ + ldapOrg: { + /** + * Id of the LdapOrg provider + */ + [id: string]: { + /** + * The prefix of the target that this matches on, e.g. + * "ldaps://ds.example.net", with no trailing slash. + */ + target: string; + + /** + * The settings to use for the bind command. If none are specified, + * the bind command is not issued. + */ + bind?: { + /** + * The DN of the user to auth as. + * + * E.g. "uid=ldap-robot,ou=robots,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The secret of the user to auth as (its password). + * + * @visibility secret + */ + secret: string; + }; + + /** + * TLS settings + */ + tls?: { + // Node TLS rejectUnauthorized + rejectUnauthorized?: boolean; + }; + + /** + * The settings that govern the reading and interpretation of users. + */ + users: { + /** + * The DN under which users are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + sizeLimit?: number; + timeLimit?: number; + derefAliases?: number; + typesOnly?: boolean; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "uid". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "uid". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. Defaults to + * "mail". + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.memberOf field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + }; + }; + + /** + * The settings that govern the reading and interpretation of groups. + */ + groups: { + /** + * The DN under which groups are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + sizeLimit?: number; + timeLimit?: number; + derefAliases?: number; + typesOnly?: boolean; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "cn". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "cn". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. Defaults to + * "description". + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.type field of the entity. Defaults to "groupType". + */ + type?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.parent field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.children field of the entity. Defaults to "member". + */ + members?: string; + }; + }; + }; + }; + }; /** * List of processor-specific options and attributes + * + * @deprecated This exists for backwards compatibility only and will be removed in the future. */ processors?: { /** diff --git a/plugins/catalog-backend-module-ldap/src/index.ts b/plugins/catalog-backend-module-ldap/src/index.ts index 243044369c..f3ffacb93a 100644 --- a/plugins/catalog-backend-module-ldap/src/index.ts +++ b/plugins/catalog-backend-module-ldap/src/index.ts @@ -22,3 +22,8 @@ export * from './processors'; export * from './ldap'; +export { + catalogModuleLdapOrgEntityProvider as default, + ldapOrgEntityProviderTransformsExtensionPoint, + type LdapOrgEntityProviderTransformsExtensionPoint, +} from './module'; diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts index 722625f3ed..ddea9de03f 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts @@ -15,26 +15,31 @@ */ import { ConfigReader } from '@backstage/config'; -import { readLdapConfig } from './config'; +import { readProviderConfigs } from './config'; describe('readLdapConfig', () => { it('applies all of the defaults', () => { const config = { - providers: [ - { - target: 'target', - users: { - dn: 'udn', - }, - groups: { - dn: 'gdn', + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + users: { + dn: 'udn', + }, + groups: { + dn: 'gdn', + }, + }, }, }, - ], + }, }; - const actual = readLdapConfig(new ConfigReader(config)); + const actual = readProviderConfigs(new ConfigReader(config)); const expected = [ { + id: 'default', target: 'target', bind: undefined, users: { @@ -76,72 +81,77 @@ describe('readLdapConfig', () => { it('reads all the values', () => { const config = { - providers: [ - { - target: 'target', - bind: { dn: 'bdn', secret: 's' }, - tls: { - rejectUnauthorized: false, - keys: '/tmp/keys.pem', - certs: '/tmp/certs.pem', - }, - users: { - dn: 'udn', - options: { - scope: 'base', - attributes: ['*'], - filter: 'f', - paged: true, - timeLimit: 42, - sizeLimit: 100, - derefAliases: 0, - typesOnly: false, - }, - set: { p: 'v' }, - map: { - rdn: 'u', - name: 'v', - description: 'd', - displayName: 'c', - email: 'm', - picture: 'p', - memberOf: 'm', - }, - }, - groups: { - dn: 'gdn', - options: { - scope: 'base', - attributes: ['*'], - filter: 'f', - paged: { - pageSize: 7, - pagePause: true, + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + bind: { dn: 'bdn', secret: 's' }, + tls: { + rejectUnauthorized: false, + keys: '/tmp/keys.pem', + certs: '/tmp/certs.pem', + }, + users: { + dn: 'udn', + options: { + scope: 'base', + attributes: ['*'], + filter: 'f', + paged: true, + timeLimit: 42, + sizeLimit: 100, + derefAliases: 0, + typesOnly: false, + }, + set: { p: 'v' }, + map: { + rdn: 'u', + name: 'v', + description: 'd', + displayName: 'c', + email: 'm', + picture: 'p', + memberOf: 'm', + }, + }, + groups: { + dn: 'gdn', + options: { + scope: 'base', + attributes: ['*'], + filter: 'f', + paged: { + pageSize: 7, + pagePause: true, + }, + timeLimit: 42, + sizeLimit: 100, + derefAliases: 1, + typesOnly: true, + }, + set: { p: 'v' }, + map: { + rdn: 'u', + name: 'v', + description: 'd', + type: 't', + displayName: 'c', + email: 'm', + picture: 'p', + memberOf: 'm', + members: 'n', + }, }, - timeLimit: 42, - sizeLimit: 100, - derefAliases: 1, - typesOnly: true, - }, - set: { p: 'v' }, - map: { - rdn: 'u', - name: 'v', - description: 'd', - type: 't', - displayName: 'c', - email: 'm', - picture: 'p', - memberOf: 'm', - members: 'n', }, }, }, - ], + }, }; - const actual = readLdapConfig(new ConfigReader(config)); + const actual = readProviderConfigs(new ConfigReader(config)); const expected = [ { + id: 'default', target: 'target', bind: { dn: 'bdn', secret: 's' }, tls: { @@ -207,30 +217,34 @@ describe('readLdapConfig', () => { it('supports multiline ldap query filter', () => { const config = { - providers: [ - { - target: 'target', - users: { - dn: 'udn', - options: { - filter: ` - (| - (cn=foo bar) - (cn=bar) - ) - `, - }, - }, - groups: { - dn: 'gdn', - options: { - filter: 'f', + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + users: { + dn: 'udn', + options: { + filter: ` + (| + (cn=foo bar) + (cn=bar) + ) + `, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + }, }, }, }, - ], + }, }; - const actual = readLdapConfig(new ConfigReader(config)); + const actual = readProviderConfigs(new ConfigReader(config)); const expected = '(|(cn=foo bar)(cn=bar))'; expect(actual[0].users.options.filter).toEqual(expected); @@ -238,64 +252,72 @@ describe('readLdapConfig', () => { it('supports a dot nested set structure', () => { const config = { - providers: [ - { - target: 'target', - users: { - dn: 'udn', - options: { - filter: 'f', - }, - set: { - 'metadata.annotations': { - a: 'b', + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + users: { + dn: 'udn', + options: { + filter: 'f', + }, + set: { + 'metadata.annotations': { + a: 'b', + }, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, }, }, }, - groups: { - dn: 'gdn', - options: { - filter: 'f', - }, - set: { - x: { a: 'b' }, - }, - }, }, - ], + }, }; - const actual = readLdapConfig(new ConfigReader(config)); + const actual = readProviderConfigs(new ConfigReader(config)); expect(actual[0].users.set).toEqual({ 'metadata.annotations': { a: 'b' } }); }); it('throws on attempts to modify the set structure', () => { const config = { - providers: [ - { - target: 'target', - users: { - dn: 'udn', - options: { - filter: 'f', - }, - set: { - x: { a: 'b' }, - }, - }, - groups: { - dn: 'gdn', - options: { - filter: 'f', - }, - set: { - x: { a: 'b' }, + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + users: { + dn: 'udn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, + }, }, }, }, - ], + }, }; - const actual = readLdapConfig(new ConfigReader(config)); + const actual = readProviderConfigs(new ConfigReader(config)); expect(() => { (actual[0].users.set as any).y = 2; diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index 08aee5feee..2bd21fd166 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { JsonValue } from '@backstage/types'; import { SearchOptions } from 'ldapjs'; @@ -27,6 +31,8 @@ import { RecursivePartial } from './util'; * @public */ export type LdapProviderConfig = { + // The id of the + id: string; // The prefix of the target that this matches on, e.g. // "ldaps://ds.example.net", with no trailing slash. target: string; @@ -39,6 +45,8 @@ export type LdapProviderConfig = { users: UserConfig; // The settings that govern the reading and interpretation of groups groups: GroupConfig; + // Schedule configuration for refresh tasks. + schedule?: TaskScheduleDefinition; }; /** @@ -184,159 +192,160 @@ const defaultConfig = { }, }; +function freeze(data: T): T { + return JSON.parse(JSON.stringify(data), (_key, value) => { + if (typeof value === 'object' && value !== null) { + Object.freeze(value); + } + return value; + }); +} + +function readTlsConfig( + c: Config | undefined, +): LdapProviderConfig['tls'] | undefined { + if (!c) { + return undefined; + } + return { + rejectUnauthorized: c.getOptionalBoolean('rejectUnauthorized'), + keys: c.getOptionalString('keys'), + certs: c.getOptionalString('certs'), + }; +} + +function readBindConfig( + c: Config | undefined, +): LdapProviderConfig['bind'] | undefined { + if (!c) { + return undefined; + } + return { + dn: c.getString('dn'), + secret: c.getString('secret'), + }; +} + +function readOptionsConfig(c: Config | undefined): SearchOptions { + if (!c) { + return {}; + } + + const paged = readOptionsPagedConfig(c); + + return { + scope: c.getOptionalString('scope') as SearchOptions['scope'], + filter: formatFilter(c.getOptionalString('filter')), + attributes: c.getOptionalStringArray('attributes'), + sizeLimit: c.getOptionalNumber('sizeLimit'), + timeLimit: c.getOptionalNumber('timeLimit'), + derefAliases: c.getOptionalNumber('derefAliases'), + typesOnly: c.getOptionalBoolean('typesOnly'), + ...(paged !== undefined ? { paged } : undefined), + }; +} + +function readOptionsPagedConfig(c: Config): SearchOptions['paged'] { + const pagedConfig = c.getOptional('paged'); + if (pagedConfig === undefined) { + return undefined; + } + + if (pagedConfig === true || pagedConfig === false) { + return pagedConfig; + } + + const pageSize = c.getOptionalNumber('paged.pageSize'); + const pagePause = c.getOptionalBoolean('paged.pagePause'); + return { + ...(pageSize !== undefined ? { pageSize } : undefined), + ...(pagePause !== undefined ? { pagePause } : undefined), + }; +} + +function readSetConfig( + c: Config | undefined, +): { [path: string]: JsonValue } | undefined { + if (!c) { + return undefined; + } + return c.get(); +} + +function readUserMapConfig( + c: Config | undefined, +): Partial { + if (!c) { + return {}; + } + + return { + rdn: c.getOptionalString('rdn'), + name: c.getOptionalString('name'), + description: c.getOptionalString('description'), + displayName: c.getOptionalString('displayName'), + email: c.getOptionalString('email'), + picture: c.getOptionalString('picture'), + memberOf: c.getOptionalString('memberOf'), + }; +} + +function readGroupMapConfig( + c: Config | undefined, +): Partial { + if (!c) { + return {}; + } + + return { + rdn: c.getOptionalString('rdn'), + name: c.getOptionalString('name'), + description: c.getOptionalString('description'), + type: c.getOptionalString('type'), + displayName: c.getOptionalString('displayName'), + email: c.getOptionalString('email'), + picture: c.getOptionalString('picture'), + memberOf: c.getOptionalString('memberOf'), + members: c.getOptionalString('members'), + }; +} + +function readUserConfig( + c: Config, +): RecursivePartial { + return { + dn: c.getString('dn'), + options: readOptionsConfig(c.getOptionalConfig('options')), + set: readSetConfig(c.getOptionalConfig('set')), + map: readUserMapConfig(c.getOptionalConfig('map')), + }; +} + +function readGroupConfig( + c: Config, +): RecursivePartial { + return { + dn: c.getString('dn'), + options: readOptionsConfig(c.getOptionalConfig('options')), + set: readSetConfig(c.getOptionalConfig('set')), + map: readGroupMapConfig(c.getOptionalConfig('map')), + }; +} + +function formatFilter(filter?: string): string | undefined { + // Remove extra whitespace between blocks to support multiline filters from the configuration + return filter?.replace(/\s*(\(|\))/g, '$1')?.trim(); +} + /** * Parses configuration. * * @param config - The root of the LDAP config hierarchy * * @public + * @deprecated This exists for backwards compatibility only and will be removed in the future. */ -export function readLdapConfig(config: Config): LdapProviderConfig[] { - function freeze(data: T): T { - return JSON.parse(JSON.stringify(data), (_key, value) => { - if (typeof value === 'object' && value !== null) { - Object.freeze(value); - } - return value; - }); - } - - function readTlsConfig( - c: Config | undefined, - ): LdapProviderConfig['tls'] | undefined { - if (!c) { - return undefined; - } - return { - rejectUnauthorized: c.getOptionalBoolean('rejectUnauthorized'), - keys: c.getOptionalString('keys'), - certs: c.getOptionalString('certs'), - }; - } - - function readBindConfig( - c: Config | undefined, - ): LdapProviderConfig['bind'] | undefined { - if (!c) { - return undefined; - } - return { - dn: c.getString('dn'), - secret: c.getString('secret'), - }; - } - - function readOptionsConfig(c: Config | undefined): SearchOptions { - if (!c) { - return {}; - } - - const paged = readOptionsPagedConfig(c); - - return { - scope: c.getOptionalString('scope') as SearchOptions['scope'], - filter: formatFilter(c.getOptionalString('filter')), - attributes: c.getOptionalStringArray('attributes'), - sizeLimit: c.getOptionalNumber('sizeLimit'), - timeLimit: c.getOptionalNumber('timeLimit'), - derefAliases: c.getOptionalNumber('derefAliases'), - typesOnly: c.getOptionalBoolean('typesOnly'), - ...(paged !== undefined ? { paged } : undefined), - }; - } - - function readOptionsPagedConfig(c: Config): SearchOptions['paged'] { - const pagedConfig = c.getOptional('paged'); - if (pagedConfig === undefined) { - return undefined; - } - - if (pagedConfig === true || pagedConfig === false) { - return pagedConfig; - } - - const pageSize = c.getOptionalNumber('paged.pageSize'); - const pagePause = c.getOptionalBoolean('paged.pagePause'); - return { - ...(pageSize !== undefined ? { pageSize } : undefined), - ...(pagePause !== undefined ? { pagePause } : undefined), - }; - } - - function readSetConfig( - c: Config | undefined, - ): { [path: string]: JsonValue } | undefined { - if (!c) { - return undefined; - } - return c.get(); - } - - function readUserMapConfig( - c: Config | undefined, - ): Partial { - if (!c) { - return {}; - } - - return { - rdn: c.getOptionalString('rdn'), - name: c.getOptionalString('name'), - description: c.getOptionalString('description'), - displayName: c.getOptionalString('displayName'), - email: c.getOptionalString('email'), - picture: c.getOptionalString('picture'), - memberOf: c.getOptionalString('memberOf'), - }; - } - - function readGroupMapConfig( - c: Config | undefined, - ): Partial { - if (!c) { - return {}; - } - - return { - rdn: c.getOptionalString('rdn'), - name: c.getOptionalString('name'), - description: c.getOptionalString('description'), - type: c.getOptionalString('type'), - displayName: c.getOptionalString('displayName'), - email: c.getOptionalString('email'), - picture: c.getOptionalString('picture'), - memberOf: c.getOptionalString('memberOf'), - members: c.getOptionalString('members'), - }; - } - - function readUserConfig( - c: Config, - ): RecursivePartial { - return { - dn: c.getString('dn'), - options: readOptionsConfig(c.getOptionalConfig('options')), - set: readSetConfig(c.getOptionalConfig('set')), - map: readUserMapConfig(c.getOptionalConfig('map')), - }; - } - - function readGroupConfig( - c: Config, - ): RecursivePartial { - return { - dn: c.getString('dn'), - options: readOptionsConfig(c.getOptionalConfig('options')), - set: readSetConfig(c.getOptionalConfig('set')), - map: readGroupMapConfig(c.getOptionalConfig('map')), - }; - } - - function formatFilter(filter?: string): string | undefined { - // Remove extra whitespace between blocks to support multiline filters from the configuration - return filter?.replace(/\s*(\(|\))/g, '$1')?.trim(); - } - +export function readLdapLegacyConfig(config: Config): LdapProviderConfig[] { const providerConfigs = config.getOptionalConfigArray('providers') ?? []; return providerConfigs.map(c => { const newConfig = { @@ -353,3 +362,40 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { return freeze(merged) as LdapProviderConfig; }); } + +/** + * Parses all configured providers. + * + * @param config - The root of the LDAP config hierarchy + * + * @public + */ +export function readProviderConfigs(config: Config): LdapProviderConfig[] { + const providersConfig = config.getOptionalConfig('catalog.providers.ldapOrg'); + if (!providersConfig) { + return []; + } + + return providersConfig.keys().map(id => { + const c = providersConfig.getConfig(id); + + const schedule = c.has('schedule') + ? readTaskScheduleDefinitionFromConfig(c.getConfig('schedule')) + : undefined; + + const newConfig = { + id, + target: trimEnd(c.getString('target'), '/'), + tls: readTlsConfig(c.getOptionalConfig('tls')), + bind: readBindConfig(c.getOptionalConfig('bind')), + users: readUserConfig(c.getConfig('users')), + groups: readGroupConfig(c.getConfig('groups')), + schedule, + }; + const merged = mergeWith({}, defaultConfig, newConfig, (_into, from) => { + // Replace arrays instead of merging, otherwise default behavior + return Array.isArray(from) ? from : undefined; + }); + return freeze(merged) as LdapProviderConfig; + }); +} diff --git a/plugins/catalog-backend-module-ldap/src/ldap/index.ts b/plugins/catalog-backend-module-ldap/src/ldap/index.ts index 6d7800fbfd..cfab5a4444 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/index.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/index.ts @@ -16,7 +16,7 @@ export { LdapClient } from './client'; export { mapStringAttr } from './util'; -export { readLdapConfig } from './config'; +export { readProviderConfigs, readLdapLegacyConfig } from './config'; export type { LdapProviderConfig, GroupConfig, diff --git a/plugins/catalog-backend-module-ldap/src/module.ts b/plugins/catalog-backend-module-ldap/src/module.ts new file mode 100644 index 0000000000..ad76b133f1 --- /dev/null +++ b/plugins/catalog-backend-module-ldap/src/module.ts @@ -0,0 +1,114 @@ +/* + * 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 { + coreServices, + createBackendModule, + createExtensionPoint, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { + GroupTransformer, + UserTransformer, +} from '@backstage/plugin-catalog-backend-module-ldap'; +import { LdapOrgEntityProvider } from './processors'; + +/** + * Interface for {@link LdapOrgEntityProviderTransformsExtensionPoint}. + * + * @public + */ +export interface LdapOrgEntityProviderTransformsExtensionPoint { + /** + * Set the function that transforms a user entry in LDAP to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + setUserTransformer( + transformer: UserTransformer | Record, + ): void; + + /** + * Set the function that transforms a group entry in LDAP to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + setGroupTransformer( + transformer: GroupTransformer | Record, + ): void; +} + +/** + * Extension point used to customize the transforms used by the module. + * + * @public + */ +export const ldapOrgEntityProviderTransformsExtensionPoint = + createExtensionPoint({ + id: 'catalog.ldapOrgEntityProvider.transforms', + }); + +/** + * Registers the LdapOrgEntityProvider with the catalog processing extension point. + * + * @public + */ +export const catalogModuleLdapOrgEntityProvider = createBackendModule({ + pluginId: 'catalog', + moduleId: 'ldapOrgEntityProvider', + register(env) { + let userTransformer: + | UserTransformer + | Record + | undefined; + let groupTransformer: + | GroupTransformer + | Record + | undefined; + + env.registerExtensionPoint(ldapOrgEntityProviderTransformsExtensionPoint, { + setUserTransformer(transformer) { + if (userTransformer) { + throw new Error('User transformer may only be set once'); + } + userTransformer = transformer; + }, + setGroupTransformer(transformer) { + if (groupTransformer) { + throw new Error('Group transformer may only be set once'); + } + groupTransformer = transformer; + }, + }); + + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: coreServices.rootConfig, + logger: coreServices.logger, + scheduler: coreServices.scheduler, + }, + async init({ catalog, config, logger, scheduler }) { + catalog.addEntityProvider( + LdapOrgEntityProvider.fromConfig(config, { + logger, + scheduler, + userTransformer: userTransformer, + groupTransformer: groupTransformer, + }), + ); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts index 97ea4a53b0..fb8f27d685 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -32,18 +32,65 @@ import { LdapClient, LdapProviderConfig, LDAP_DN_ANNOTATION, - readLdapConfig, readLdapOrg, UserTransformer, } from '../ldap'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { readLdapLegacyConfig, readProviderConfigs } from '../ldap'; /** * Options for {@link LdapOrgEntityProvider}. * * @public */ -export interface LdapOrgEntityProviderOptions { +export type LdapOrgEntityProviderOptions = + | LdapOrgEntityProviderLegacyOptions + | { + /** + * The logger to use. + */ + logger: LoggerService; + + /** + * The refresh schedule to use. + * + * @remarks + * + * If you pass in 'manual', you are responsible for calling the `read` method + * manually at some interval. + * + * But more commonly you will pass in the result of + * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * to enable automatic scheduling of tasks. + */ + schedule?: 'manual' | TaskRunner; + + /** + * Scheduler used to schedule refreshes based on + * the schedule config. + */ + scheduler?: PluginTaskScheduler; + + /** + * The function that transforms a user entry in msgraph to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + userTransformer?: UserTransformer | Record; + + /** + * The function that transforms a group entry in msgraph to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + groupTransformer?: GroupTransformer | Record; + }; + +/** + * Options for {@link LdapOrgEntityProvider}. + * + * @public + * @deprecated This interface exists for backwards compatibility only and will be removed in the future. + */ +export interface LdapOrgEntityProviderLegacyOptions { /** * A unique, stable identifier for this provider. * @@ -109,12 +156,68 @@ export class LdapOrgEntityProvider implements EntityProvider { static fromConfig( configRoot: Config, options: LdapOrgEntityProviderOptions, + ): LdapOrgEntityProvider[] { + if ('id' in options) { + return [LdapOrgEntityProvider.fromLegacyConfig(configRoot, options)]; + } + + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + + function getTransformer( + id: string, + transformers?: T | Record, + ): T | undefined { + if (['undefined', 'function'].includes(typeof transformers)) { + return transformers as T; + } + + return (transformers as Record)[id]; + } + + return readProviderConfigs(configRoot).map(providerConfig => { + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for LdapOrgEntityProvider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + + const provider = new LdapOrgEntityProvider({ + id: providerConfig.id, + provider: providerConfig, + logger: options.logger, + userTransformer: getTransformer( + providerConfig.id, + options.userTransformer, + ), + groupTransformer: getTransformer( + providerConfig.id, + options.groupTransformer, + ), + }); + + if (taskRunner !== 'manual') { + provider.schedule(taskRunner); + } + + return provider; + }); + } + + static fromLegacyConfig( + configRoot: Config, + options: LdapOrgEntityProviderLegacyOptions, ): LdapOrgEntityProvider { // TODO(freben): Deprecate the old catalog.processors.ldapOrg config const config = configRoot.getOptionalConfig('ldap') || configRoot.getOptionalConfig('catalog.processors.ldapOrg'); - const providers = config ? readLdapConfig(config) : []; + const providers = config ? readLdapLegacyConfig(config) : []; const provider = providers.find(p => options.target === p.target); if (!provider) { throw new TypeError( @@ -134,7 +237,9 @@ export class LdapOrgEntityProvider implements EntityProvider { logger, }); - result.schedule(options.schedule); + if (options.schedule !== 'manual') { + result.schedule(options.schedule); + } return result; } @@ -206,14 +311,10 @@ export class LdapOrgEntityProvider implements EntityProvider { markCommitComplete(); } - private schedule(schedule: LdapOrgEntityProviderOptions['schedule']) { - if (schedule === 'manual') { - return; - } - + private schedule(taskRunner: TaskRunner) { this.scheduleFn = async () => { const id = `${this.getProviderName()}:refresh`; - await schedule.run({ + await taskRunner.run({ id, fn: async () => { const logger = this.options.logger.child({ diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts index c920968fc1..acbe128e0b 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts @@ -19,7 +19,7 @@ import { GroupTransformer, LdapClient, LdapProviderConfig, - readLdapConfig, + readLdapLegacyConfig, readLdapOrg, UserTransformer, } from '../ldap'; @@ -56,7 +56,7 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { configRoot.getOptionalConfig('catalog.processors.ldapOrg'); return new LdapOrgReaderProcessor({ ...options, - providers: config ? readLdapConfig(config) : [], + providers: config ? readLdapLegacyConfig(config) : [], }); } diff --git a/plugins/catalog-backend-module-ldap/src/processors/index.ts b/plugins/catalog-backend-module-ldap/src/processors/index.ts index 5ed0095c3e..1a0a6a69c8 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/index.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/index.ts @@ -15,5 +15,8 @@ */ export { LdapOrgEntityProvider } from './LdapOrgEntityProvider'; -export type { LdapOrgEntityProviderOptions } from './LdapOrgEntityProvider'; +export type { + LdapOrgEntityProviderOptions, + LdapOrgEntityProviderLegacyOptions, +} from './LdapOrgEntityProvider'; export { LdapOrgReaderProcessor } from './LdapOrgReaderProcessor'; From 31ecd983a0b15c9fdeb3090174308ee51de58f71 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 12:03:11 +0200 Subject: [PATCH 087/118] feat: add logs and finally block Signed-off-by: ElaineDeMattosSilvaB --- .../src/actions/gitlabPipelineTrigger.ts | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts index 2edb529ea7..6937fe44aa 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts @@ -55,36 +55,43 @@ export const createTriggerGitlabPipelineAction = (options: { output: pipelineOutputProperties, }, async handler(ctx) { + let pipelineTokenResponse: PipelineTriggerTokenSchema | null = null; + + const { repoUrl, projectId, tokenDescription, token, branch } = + commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input); + + const { host } = parseRepoUrl(repoUrl, integrations); + const api = getClient({ host, integrations, token }); + try { - const { repoUrl, projectId, tokenDescription, token, branch } = - commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input); + // Create a pipeline token + pipelineTokenResponse = (await api.PipelineTriggerTokens.create( + projectId, + tokenDescription, + )) as PipelineTriggerTokenSchema; - const { host } = parseRepoUrl(repoUrl, integrations); - const api = getClient({ host, integrations, token }); - - // Get a pipeline token - const createdPipelineTokenResponse = - (await api.PipelineTriggerTokens.create( - projectId, - tokenDescription, - )) as PipelineTriggerTokenSchema; - - if (!createdPipelineTokenResponse.token) { + if (!pipelineTokenResponse.token) { + ctx.logger.error('Failed to create pipeline token.'); return; } + ctx.logger.info( + `Pipeline token id ${pipelineTokenResponse.id} created.`, + ); + // Use the pipeline token to trigger the pipeline in the project const pipelineTriggerResponse = (await api.PipelineTriggerTokens.trigger( projectId, branch, - createdPipelineTokenResponse.token, + pipelineTokenResponse.token, )) as ExpandedPipelineSchema; - // Delete the pipeline token - await api.PipelineTriggerTokens.remove( - projectId, - createdPipelineTokenResponse.id, - ); + if (!pipelineTriggerResponse.id) { + ctx.logger.error('Failed to trigger pipeline.'); + return; + } + + ctx.logger.info(`Pipeline id ${pipelineTriggerResponse.id} triggered.`); ctx.output('pipelineUrl', pipelineTriggerResponse.web_url); } catch (error: any) { @@ -96,6 +103,23 @@ export const createTriggerGitlabPipelineAction = (options: { } // Handling other errors throw new InputError(`Failed to trigger Pipeline: ${error.message}`); + } finally { + // Delete the pipeline token if it was created + if (pipelineTokenResponse && pipelineTokenResponse.id) { + try { + await api.PipelineTriggerTokens.remove( + projectId, + pipelineTokenResponse.id, + ); + ctx.logger.info( + `Deleted pipeline token ${pipelineTokenResponse.id}.`, + ); + } catch (error: any) { + ctx.logger.error( + `Failed to delete pipeline token id ${pipelineTokenResponse.id}.`, + ); + } + } } }, }); From c22bc6d5a23c48b158bc114db6a2505bdc1622f2 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 12:04:01 +0200 Subject: [PATCH 088/118] feat: add tests Signed-off-by: ElaineDeMattosSilvaB --- .../src/actions/gitlabPipelineTrigger.test.ts | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts new file mode 100644 index 0000000000..a7412032a4 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts @@ -0,0 +1,235 @@ +/* + * 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 { ConfigReader } from '@backstage/core-app-api'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createTriggerGitlabPipelineAction } from './gitlabPipelineTrigger'; + +const mockGitlabClient = { + PipelineTriggerTokens: { + create: jest.fn(), + trigger: jest.fn(), + remove: jest.fn(), + }, +}; +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:pipeline:trigger', () => { + beforeEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + jest.useFakeTimers({ + now: new Date(1988, 5, 3, 12, 0, 0), + }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'glpat-abcdef', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createTriggerGitlabPipelineAction({ integrations }); + + it('should return a Pipeline Token Id', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'My cool pipeline token', + branch: 'main', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockResolvedValue({ + id: 42, + description: 'My cool pipeline token', + createdAt: new Date().toISOString(), + last_used: null, + token: 'glptt-abcdef', + updated_at: new Date().toISOString(), + owner: null, + }); + + mockGitlabClient.PipelineTriggerTokens.trigger.mockResolvedValue({ + id: 99, + web_url: 'https://gitlab.com/hangar18-/pipelines/99', + }); + + await action.handler({ + ...mockContext, + }); + + expect(mockGitlabClient.PipelineTriggerTokens.create).toHaveBeenCalledWith( + 123, + 'My cool pipeline token', + ); + + expect(mockGitlabClient.PipelineTriggerTokens.trigger).toHaveBeenCalledWith( + 123, + 'main', + 'glptt-abcdef', + ); + + expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( + 123, + 42, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'pipelineUrl', + 'https://gitlab.com/hangar18-/pipelines/99', + ); + }); + + it('should throw error if pipeline token cannot be created', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'My cool pipeline token', + branch: 'main', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockRejectedValue( + new Error('Failed to create token'), + ); + + await expect( + action.handler({ + ...mockContext, + }), + ).rejects.toThrow('Failed to create token'); + + expect(mockGitlabClient.PipelineTriggerTokens.create).toHaveBeenCalledWith( + 123, + 'My cool pipeline token', + ); + + expect( + mockGitlabClient.PipelineTriggerTokens.trigger, + ).not.toHaveBeenCalled(); + + expect( + mockGitlabClient.PipelineTriggerTokens.remove, + ).not.toHaveBeenCalled(); + }); + + it('throw error if pipeline cannot be triggered', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'My cool pipeline token', + branch: 'main', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockResolvedValue({ + id: 42, + description: 'My cool pipeline token', + createdAt: new Date().toISOString(), + last_used: null, + token: 'glptt-abcdef', + updated_at: new Date().toISOString(), + owner: null, + }); + + mockGitlabClient.PipelineTriggerTokens.trigger.mockRejectedValue( + new Error('Failed to trigger pipeline'), + ); + + await expect( + action.handler({ + ...mockContext, + }), + ).rejects.toThrow('Failed to trigger pipeline'); + + expect(mockGitlabClient.PipelineTriggerTokens.create).toHaveBeenCalledWith( + 123, + 'My cool pipeline token', + ); + + expect(mockGitlabClient.PipelineTriggerTokens.trigger).toHaveBeenCalledWith( + 123, + 'main', + 'glptt-abcdef', + ); + + expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( + 123, + 42, + ); + }); + it('should clean up pipeline token on failure', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'My cool pipeline token', + branch: 'main', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockResolvedValue({ + id: 42, + description: 'My cool pipeline token', + createdAt: new Date().toISOString(), + last_used: null, + token: 'glptt-abcdef', + updated_at: new Date().toISOString(), + owner: null, + }); + + mockGitlabClient.PipelineTriggerTokens.trigger.mockRejectedValue( + new Error('Failed to trigger pipeline'), + ); + + await expect( + action.handler({ + ...mockContext, + }), + ).rejects.toThrow('Failed to trigger pipeline'); + + expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( + 123, + 42, + ); + }); +}); From 5c5622290e4285ef5b8581734f6132ddc4d1f849 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 12:06:05 +0200 Subject: [PATCH 089/118] chore: rename files in the gitlab module Signed-off-by: ElaineDeMattosSilvaB --- ...t.ts => gitlabGroupEnsureExists.examples.test.ts} | 6 +++--- ...amples.ts => gitlabGroupEnsureExists.examples.ts} | 0 ...ction.test.ts => gitlabGroupEnsureExists.test.ts} | 4 ++-- ...ureExistsAction.ts => gitlabGroupEnsureExists.ts} | 8 ++++---- ...ion.examples.ts => gitlabIssueCreate.examples.ts} | 0 ...IssueAction.test.ts => gitlabIssueCreate.test.ts} | 4 ++-- ...eateGitlabIssueAction.ts => gitlabIssueCreate.ts} | 2 +- ... gitlabProjectAccessTokenCreate.examples.test.ts} | 6 +++--- ...ts => gitlabProjectAccessTokenCreate.examples.ts} | 0 ...enAction.ts => gitlabProjectAccessTokenCreate.ts} | 2 +- ... gitlabProjectDeployTokenCreate.examples.test.ts} | 8 ++++---- ...ts => gitlabProjectDeployTokenCreate.examples.ts} | 0 ...est.ts => gitlabProjectDeployTokenCreate.test.ts} | 6 +++--- ...enAction.ts => gitlabProjectDeployTokenCreate.ts} | 10 +++++----- ... => gitlabProjectVariableCreate.examples.test.ts} | 8 ++++---- ...es.ts => gitlabProjectVariableCreate.examples.ts} | 0 ...iableAction.ts => gitlabProjectVariableCreate.ts} | 8 ++++---- .../src/actions/index.ts | 12 ++++++------ 18 files changed, 42 insertions(+), 42 deletions(-) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabGroupEnsureExistsAction.examples.test.ts => gitlabGroupEnsureExists.examples.test.ts} (96%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabGroupEnsureExistsAction.examples.ts => gitlabGroupEnsureExists.examples.ts} (100%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabGroupEnsureExistsAction.test.ts => gitlabGroupEnsureExists.test.ts} (97%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabGroupEnsureExistsAction.ts => gitlabGroupEnsureExists.ts} (97%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabIssueAction.examples.ts => gitlabIssueCreate.examples.ts} (100%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabIssueAction.test.ts => gitlabIssueCreate.test.ts} (98%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabIssueAction.ts => gitlabIssueCreate.ts} (99%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectAccessTokenAction.examples.test.ts => gitlabProjectAccessTokenCreate.examples.test.ts} (95%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectAccessTokenAction.examples.ts => gitlabProjectAccessTokenCreate.examples.ts} (100%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectAccessTokenAction.ts => gitlabProjectAccessTokenCreate.ts} (98%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectDeployTokenAction.examples.test.ts => gitlabProjectDeployTokenCreate.examples.test.ts} (96%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectDeployTokenAction.examples.ts => gitlabProjectDeployTokenCreate.examples.ts} (100%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectDeployTokenAction.test.ts => gitlabProjectDeployTokenCreate.test.ts} (96%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectDeployTokenAction.ts => gitlabProjectDeployTokenCreate.ts} (97%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectVariableAction.examples.test.ts => gitlabProjectVariableCreate.examples.test.ts} (97%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectVariableAction.examples.ts => gitlabProjectVariableCreate.examples.ts} (100%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectVariableAction.ts => gitlabProjectVariableCreate.ts} (97%) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts similarity index 96% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.examples.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts index 2d077c7400..b9377fd97b 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { createGitlabGroupEnsureExistsAction } from './createGitlabGroupEnsureExistsAction'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import yaml from 'yaml'; -import { examples } from './createGitlabGroupEnsureExistsAction.examples'; +import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; +import { examples } from './gitlabGroupEnsureExists.examples'; const mockGitlabClient = { Groups: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts similarity index 97% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts index 4dc0305c2e..a92637ba46 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { createGitlabGroupEnsureExistsAction } from './createGitlabGroupEnsureExistsAction'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; const mockGitlabClient = { Groups: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts similarity index 97% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts index 2cf583a95d..29f260fb75 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { Gitlab } from '@gitbeaker/node'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { GroupSchema } from '@gitbeaker/core/dist/types/resources/Groups'; +import { Gitlab } from '@gitbeaker/node'; +import { z } from 'zod'; import commonGitlabConfig from '../commonGitlabConfig'; import { getToken } from '../util'; -import { z } from 'zod'; -import { examples } from './createGitlabGroupEnsureExistsAction.examples'; +import { examples } from './gitlabGroupEnsureExists.examples'; /** * Creates an `gitlab:group:ensureExists` Scaffolder action. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts similarity index 98% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts index feea436a1e..af359c39b5 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { createGitlabIssueAction, IssueType } from './createGitlabIssueAction'; import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabIssueAction, IssueType } from './gitlabIssueCreate'; const mockGitlabClient = { Issues: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts similarity index 99% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts index 894caf635c..97ac3cd6fb 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts @@ -18,7 +18,7 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import commonGitlabConfig from '../commonGitlabConfig'; -import { examples } from './createGitlabIssueAction.examples'; +import { examples } from './gitlabIssueCreate.examples'; import { z } from 'zod'; import { checkEpicScope, convertDate, getClient, parseRepoUrl } from '../util'; import { Gitlab, CreateIssueOptions, IssueSchema } from '@gitbeaker/rest'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.test.ts similarity index 95% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.examples.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.test.ts index 5afeaba8ae..811926cc82 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.test.ts @@ -15,10 +15,10 @@ */ import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import yaml from 'yaml'; -import { createGitlabProjectAccessTokenAction } from './createGitlabProjectAccessTokenAction'; // Adjust the import based on your project structure -import { examples } from './createGitlabProjectAccessTokenAction.examples'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import yaml from 'yaml'; +import { createGitlabProjectAccessTokenAction } from './gitlabProjectAccessTokenCreate'; // Adjust the import based on your project structure +import { examples } from './gitlabProjectAccessTokenCreate.examples'; import { DateTime } from 'luxon'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts similarity index 98% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts index 2193dbb02f..5d31686a6e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts @@ -21,7 +21,7 @@ import { AccessTokenScopes, Gitlab } from '@gitbeaker/rest'; import { DateTime } from 'luxon'; import { z } from 'zod'; import { getToken } from '../util'; -import { examples } from './createGitlabProjectAccessTokenAction.examples'; +import { examples } from './gitlabProjectAccessTokenCreate.examples'; /** * Creates a `gitlab:projectAccessToken:create` Scaffolder action. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.examples.test.ts similarity index 96% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.examples.test.ts index dd3b694670..d56d6de556 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { createGitlabProjectDeployTokenAction } from './createGitlabProjectDeployTokenAction'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import yaml from 'yaml'; -import { examples } from './createGitlabProjectDeployTokenAction.examples'; +import { createGitlabProjectDeployTokenAction } from './gitlabProjectDeployTokenCreate'; +import { examples } from './gitlabProjectDeployTokenCreate.examples'; const mockGitlabClient = { ProjectDeployTokens: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.examples.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.test.ts similarity index 96% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.test.ts index cab72c9a6d..146692006b 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { createGitlabProjectDeployTokenAction } from './createGitlabProjectDeployTokenAction'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabProjectDeployTokenAction } from './gitlabProjectDeployTokenCreate'; const mockGitlabClient = { ProjectDeployTokens: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts similarity index 97% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts index 87d4314b6f..79ecc0de4e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { Gitlab } from '@gitbeaker/node'; +import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { DeployTokenScope } from '@gitbeaker/core/dist/types/templates/ResourceDeployTokens'; +import { Gitlab } from '@gitbeaker/node'; +import { z } from 'zod'; import commonGitlabConfig from '../commonGitlabConfig'; import { getToken } from '../util'; -import { InputError } from '@backstage/errors'; -import { z } from 'zod'; -import { examples } from './createGitlabProjectDeployTokenAction.examples'; +import { examples } from './gitlabProjectDeployTokenCreate.examples'; /** * Creates a `gitlab:projectDeployToken:create` Scaffolder action. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts similarity index 97% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts index d1c37ccd9d..1eeb01ca91 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { createGitlabProjectVariableAction } from './createGitlabProjectVariableAction'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import yaml from 'yaml'; -import { examples } from './createGitlabProjectVariableAction.examples'; +import { createGitlabProjectVariableAction } from './gitlabProjectVariableCreate'; +import { examples } from './gitlabProjectVariableCreate.examples'; const mockGitlabClient = { ProjectVariables: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts similarity index 97% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts index e09a074701..a07ec747fa 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { Gitlab } from '@gitbeaker/node'; -import { getToken } from '../util'; -import commonGitlabConfig from '../commonGitlabConfig'; import { z } from 'zod'; -import { examples } from './createGitlabProjectVariableAction.examples'; +import commonGitlabConfig from '../commonGitlabConfig'; +import { getToken } from '../util'; +import { examples } from './gitlabProjectVariableCreate.examples'; /** * Creates a `gitlab:projectVariable:create` Scaffolder action. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts index fb80a43ff4..c5c24772ef 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './createGitlabGroupEnsureExistsAction'; -export * from './createGitlabIssueAction'; -export * from './createGitlabProjectAccessTokenAction'; -export * from './createGitlabProjectDeployTokenAction'; -export * from './createGitlabProjectVariableAction'; export * from './gitlab'; +export * from './gitlabGroupEnsureExists'; +export * from './gitlabIssueCreate'; export * from './gitlabMergeRequest'; -export * from './gitlabRepoPush'; export * from './gitlabPipelineTrigger'; +export * from './gitlabProjectAccessTokenCreate'; +export * from './gitlabProjectDeployTokenCreate'; +export * from './gitlabProjectVariableCreate'; +export * from './gitlabRepoPush'; From 595819b6adb2f12a99c4db3b9ddce964dcae2886 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 12:58:46 +0200 Subject: [PATCH 090/118] fix: add changes to api report Signed-off-by: ElaineDeMattosSilvaB --- .../api-report.md | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index fdffba1c7e..5b69210050 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -33,11 +33,11 @@ export const createGitlabIssueAction: (options: { projectId: number; labels?: string | undefined; description?: string | undefined; - weight?: number | undefined; token?: string | undefined; + weight?: number | undefined; assignees?: number[] | undefined; - createdAt?: string | undefined; confidential?: boolean | undefined; + createdAt?: string | undefined; milestoneId?: number | undefined; epicId?: number | undefined; dueDate?: string | undefined; @@ -61,8 +61,8 @@ export const createGitlabProjectAccessTokenAction: (options: { projectId: string | number; name?: string | undefined; token?: string | undefined; - scopes?: string[] | undefined; expiresAt?: string | undefined; + scopes?: string[] | undefined; accessLevel?: number | undefined; }, { @@ -78,8 +78,8 @@ export const createGitlabProjectDeployTokenAction: (options: { name: string; repoUrl: string; projectId: string | number; - username?: string | undefined; token?: string | undefined; + username?: string | undefined; scopes?: string[] | undefined; }, { @@ -118,7 +118,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'update' | 'create' | 'delete' | undefined; }, JsonObject >; @@ -149,8 +149,8 @@ export function createPublishGitlabAction(options: { squash_option?: | 'always' | 'never' - | 'default_on' | 'default_off' + | 'default_on' | undefined; topics?: string[] | undefined; visibility?: 'internal' | 'private' | 'public' | undefined; @@ -193,7 +193,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'update' | 'create' | 'delete' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -201,6 +201,22 @@ export const createPublishGitlabMergeRequestAction: (options: { JsonObject >; +// @public +export const createTriggerGitlabPipelineAction: (options: { + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + repoUrl: string; + branch: string; + projectId: number; + tokenDescription: string; + token?: string | undefined; + }, + { + pipelineUrl: string; + } +>; + // @public const gitlabModule: () => BackendFeature; export default gitlabModule; From 829e0ec80e35ed2204ab41bdd841737c411a4693 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 12:59:12 +0200 Subject: [PATCH 091/118] feat: add changeset Signed-off-by: ElaineDeMattosSilvaB --- .changeset/soft-flies-live.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/soft-flies-live.md diff --git a/.changeset/soft-flies-live.md b/.changeset/soft-flies-live.md new file mode 100644 index 0000000000..2de68f12ee --- /dev/null +++ b/.changeset/soft-flies-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': minor +--- + +Add new Scaffolder action to trigger GitLab pipelines. From 73e7c13a3b1d84ae0758710c09e02df3ff4961b0 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Mon, 27 May 2024 14:01:30 +0200 Subject: [PATCH 092/118] Update tall-lies-fetch.md Signed-off-by: Ben Lambert --- .changeset/tall-lies-fetch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tall-lies-fetch.md b/.changeset/tall-lies-fetch.md index 391aa95aa5..a4484a5687 100644 --- a/.changeset/tall-lies-fetch.md +++ b/.changeset/tall-lies-fetch.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog': patch --- -Variable 'catalogTranslationRef' is exported in translation.ts, but it was forgotten to also add it to the alpha entrypoint, so the code never became "visible" +Export `catalogTranslationRef` under `/alpha` From 11540f4f707c457241a2c5fcd7277333b50fcb98 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Mon, 27 May 2024 14:04:05 +0200 Subject: [PATCH 093/118] Update strong-moose-work.md Signed-off-by: Ben Lambert --- .changeset/strong-moose-work.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/strong-moose-work.md b/.changeset/strong-moose-work.md index 20d35da7f0..443b708e51 100644 --- a/.changeset/strong-moose-work.md +++ b/.changeset/strong-moose-work.md @@ -1,5 +1,5 @@ --- -'@backstage/repo-tools': minor +'@backstage/repo-tools': patch --- -Add --client-additional-properties option to generate command to pass properties to @openapitools/openapi-generator-cli +Add `--client-additional-properties` option to `openapi generate` command From f67ae7c6c2942d607b7b6612dcbb54b258127245 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 14:50:55 +0200 Subject: [PATCH 094/118] fix: add api-report.md Signed-off-by: ElaineDeMattosSilvaB --- .../scaffolder-backend-module-gitlab/api-report.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index 5b69210050..102b2e47b0 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -33,11 +33,11 @@ export const createGitlabIssueAction: (options: { projectId: number; labels?: string | undefined; description?: string | undefined; - token?: string | undefined; weight?: number | undefined; + token?: string | undefined; assignees?: number[] | undefined; - confidential?: boolean | undefined; createdAt?: string | undefined; + confidential?: boolean | undefined; milestoneId?: number | undefined; epicId?: number | undefined; dueDate?: string | undefined; @@ -61,8 +61,8 @@ export const createGitlabProjectAccessTokenAction: (options: { projectId: string | number; name?: string | undefined; token?: string | undefined; - expiresAt?: string | undefined; scopes?: string[] | undefined; + expiresAt?: string | undefined; accessLevel?: number | undefined; }, { @@ -78,8 +78,8 @@ export const createGitlabProjectDeployTokenAction: (options: { name: string; repoUrl: string; projectId: string | number; - token?: string | undefined; username?: string | undefined; + token?: string | undefined; scopes?: string[] | undefined; }, { @@ -118,7 +118,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'create' | 'delete' | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; }, JsonObject >; @@ -193,7 +193,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'create' | 'delete' | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -206,8 +206,8 @@ export const createTriggerGitlabPipelineAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - repoUrl: string; branch: string; + repoUrl: string; projectId: number; tokenDescription: string; token?: string | undefined; From 0665b7ed50d63485e8b654fce2f1415857a234c4 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 27 May 2024 07:48:02 +0200 Subject: [PATCH 095/118] refactor(backend-plugin-api): rename factory configs to options Signed-off-by: Camila Belo --- .changeset/warm-bees-hope.md | 5 ++ packages/backend-plugin-api/api-report.md | 49 +++++++++++-------- .../src/wiring/factories.ts | 30 ++++++------ .../backend-plugin-api/src/wiring/index.ts | 34 +++++++++++-- 4 files changed, 79 insertions(+), 39 deletions(-) create mode 100644 .changeset/warm-bees-hope.md diff --git a/.changeset/warm-bees-hope.md b/.changeset/warm-bees-hope.md new file mode 100644 index 0000000000..78b77cf5fb --- /dev/null +++ b/.changeset/warm-bees-hope.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': patch +--- + +We renamed `BackendPluginConfig`, `BackendModuleConfig`, and `ExtensionPointConfig` respectively to `CreateBackendPluginOptions`, `CreateBackendModuleOptions`, and `CreateExtensionPointOptions` in order to standardize frontend and backend factories signatures. diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 134e4b61da..5e59378760 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -70,13 +70,8 @@ export interface BackendFeature { $$type: '@backstage/BackendFeature'; } -// @public -export interface BackendModuleConfig { - moduleId: string; - pluginId: string; - // (undocumented) - register(reg: BackendModuleRegistrationPoints): void; -} +// @public @deprecated (undocumented) +export type BackendModuleConfig = CreateBackendModuleOptions; // @public export interface BackendModuleRegistrationPoints { @@ -98,12 +93,8 @@ export interface BackendModuleRegistrationPoints { }): void; } -// @public -export interface BackendPluginConfig { - pluginId: string; - // (undocumented) - register(reg: BackendPluginRegistrationPoints): void; -} +// @public @deprecated (undocumented) +export type BackendPluginConfig = CreateBackendPluginOptions; // @public export interface BackendPluginRegistrationPoints { @@ -223,19 +214,39 @@ export namespace coreServices { // @public export function createBackendModule( - config: BackendModuleConfig, + options: CreateBackendModuleOptions, ): () => BackendFeature; +// @public +export interface CreateBackendModuleOptions { + moduleId: string; + pluginId: string; + // (undocumented) + register(reg: BackendModuleRegistrationPoints): void; +} + // @public export function createBackendPlugin( - config: BackendPluginConfig, + options: CreateBackendPluginOptions, ): () => BackendFeature; +// @public +export interface CreateBackendPluginOptions { + pluginId: string; + // (undocumented) + register(reg: BackendPluginRegistrationPoints): void; +} + // @public export function createExtensionPoint( - config: ExtensionPointConfig, + options: CreateExtensionPointOptions, ): ExtensionPoint; +// @public +export interface CreateExtensionPointOptions { + id: string; +} + // @public export function createServiceFactory< TService, @@ -320,10 +331,8 @@ export type ExtensionPoint = { $$type: '@backstage/ExtensionPoint'; }; -// @public -export interface ExtensionPointConfig { - id: string; -} +// @public @deprecated (undocumented) +export type ExtensionPointConfig = CreateExtensionPointOptions; // @public (undocumented) export interface HttpAuthService { diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts index 9fa0f87bab..d0199e775a 100644 --- a/packages/backend-plugin-api/src/wiring/factories.ts +++ b/packages/backend-plugin-api/src/wiring/factories.ts @@ -30,7 +30,7 @@ import { * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} */ -export interface ExtensionPointConfig { +export interface CreateExtensionPointOptions { /** * The ID of this extension point. * @@ -46,15 +46,15 @@ export interface ExtensionPointConfig { * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} */ export function createExtensionPoint( - config: ExtensionPointConfig, + options: CreateExtensionPointOptions, ): ExtensionPoint { return { - id: config.id, + id: options.id, get T(): T { throw new Error(`tried to read ExtensionPoint.T of ${this}`); }, toString() { - return `extensionPoint{${config.id}}`; + return `extensionPoint{${options.id}}`; }, $$type: '@backstage/ExtensionPoint', }; @@ -67,7 +67,7 @@ export function createExtensionPoint( * @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 interface BackendPluginConfig { +export interface CreateBackendPluginOptions { /** * The ID of this plugin. * @@ -85,7 +85,7 @@ export interface BackendPluginConfig { * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} */ export function createBackendPlugin( - config: BackendPluginConfig, + options: CreateBackendPluginOptions, ): () => BackendFeature { const factory: BackendFeatureFactory = () => { let registrations: InternalBackendPluginRegistration[]; @@ -102,7 +102,7 @@ export function createBackendPlugin( let init: InternalBackendPluginRegistration['init'] | undefined = undefined; - config.register({ + options.register({ registerExtensionPoint(ext, impl) { if (init) { throw new Error( @@ -124,14 +124,14 @@ export function createBackendPlugin( if (!init) { throw new Error( - `registerInit was not called by register in ${config.pluginId}`, + `registerInit was not called by register in ${options.pluginId}`, ); } registrations = [ { type: 'plugin', - pluginId: config.pluginId, + pluginId: options.pluginId, extensionPoints, init, }, @@ -152,7 +152,7 @@ export function createBackendPlugin( * @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 interface BackendModuleConfig { +export interface CreateBackendModuleOptions { /** * Should exactly match the `id` of the plugin that the module extends. * @@ -175,7 +175,7 @@ export interface BackendModuleConfig { * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} */ export function createBackendModule( - config: BackendModuleConfig, + options: CreateBackendModuleOptions, ): () => BackendFeature { const factory: BackendFeatureFactory = () => { let registrations: InternalBackendModuleRegistration[]; @@ -192,7 +192,7 @@ export function createBackendModule( let init: InternalBackendModuleRegistration['init'] | undefined = undefined; - config.register({ + options.register({ registerExtensionPoint(ext, impl) { if (init) { throw new Error( @@ -214,15 +214,15 @@ export function createBackendModule( if (!init) { throw new Error( - `registerInit was not called by register in ${config.moduleId} module for ${config.pluginId}`, + `registerInit was not called by register in ${options.moduleId} module for ${options.pluginId}`, ); } registrations = [ { type: 'module', - pluginId: config.pluginId, - moduleId: config.moduleId, + pluginId: options.pluginId, + moduleId: options.moduleId, extensionPoints, init, }, diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts index 49f15c0b55..5197cec050 100644 --- a/packages/backend-plugin-api/src/wiring/index.ts +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -14,18 +14,44 @@ * limitations under the License. */ -export type { - BackendModuleConfig, - BackendPluginConfig, - ExtensionPointConfig, +import type { + CreateBackendPluginOptions, + CreateBackendModuleOptions, + CreateExtensionPointOptions, } from './factories'; + export { createBackendModule, createBackendPlugin, createExtensionPoint, } from './factories'; + export type { BackendModuleRegistrationPoints, BackendPluginRegistrationPoints, ExtensionPoint, } from './types'; + +export type { + CreateBackendPluginOptions, + CreateBackendModuleOptions, + CreateExtensionPointOptions, +}; + +/** + * @public + * @deprecated Use {@link CreateBackendPluginOptions} instead. + */ +export type BackendPluginConfig = CreateBackendPluginOptions; + +/** + * @public + * @deprecated Use {@link CreateBackendModuleOptions} instead. + */ +export type BackendModuleConfig = CreateBackendModuleOptions; + +/** + * @public + * @deprecated Use {@link CreateExtensionPointOptions} instead. + */ +export type ExtensionPointConfig = CreateExtensionPointOptions; From 56ebcfc1f30c498adf753cfcaa80f09a2948bb6a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 13:04:10 +0000 Subject: [PATCH 096/118] chore(deps): update actions/checkout action to v4.1.6 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/api-breaking-changes.yml | 2 +- .github/workflows/automate_changeset_feedback.yml | 2 +- .github/workflows/automate_merge_message.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/deploy_docker-image.yml | 2 +- .github/workflows/deploy_microsite.yml | 2 +- .github/workflows/deploy_nightly.yml | 2 +- .github/workflows/deploy_packages.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- .github/workflows/sync_code-formatting.yml | 2 +- .github/workflows/sync_dependabot-changesets.yml | 2 +- .github/workflows/sync_release-manifest.yml | 4 ++-- .github/workflows/sync_renovate-changesets.yml | 2 +- .github/workflows/sync_snyk-github-issues.yml | 2 +- .github/workflows/sync_snyk-monitor.yml | 2 +- .github/workflows/sync_version-packages.yml | 2 +- .github/workflows/uffizzi-build.yml | 4 ++-- .github/workflows/verify_accessibility.yml | 2 +- .github/workflows/verify_codeql.yml | 2 +- .github/workflows/verify_docs-quality.yml | 2 +- .github/workflows/verify_e2e-kubernetes.yml | 2 +- .github/workflows/verify_e2e-linux.yml | 2 +- .github/workflows/verify_e2e-techdocs.yml | 2 +- .github/workflows/verify_e2e-windows.yml | 2 +- .github/workflows/verify_fossa.yml | 2 +- .github/workflows/verify_microsite.yml | 2 +- .github/workflows/verify_microsite_accessibility.yml | 2 +- .github/workflows/verify_storybook.yml | 2 +- .github/workflows/verify_windows.yml | 2 +- 29 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 5f456a9b0b..0191d1f0af 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index 6ac327bcd6..95b0a808a1 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -27,7 +27,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index 56ab3283ac..a53369c884 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: ref: '${{ github.event.pull_request.merge_commit_sha }}' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd3b66ddaa..00181adb5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -68,7 +68,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -206,7 +206,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: fetch master branch run: git fetch origin master diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 19c58a8a60..ddf01fa49c 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -25,7 +25,7 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: path: backstage ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }} diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 2554e3eeb8..4a1feed0ce 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index 6ec5b03d56..02a1c65681 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index cd2947a565..04badd65b0 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -74,7 +74,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -158,7 +158,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a51e3fefe8..8d5b2522af 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -34,7 +34,7 @@ jobs: egress-policy: audit - name: 'Checkout code' - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: persist-credentials: false diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index dce7217201..bb2e00ffa6 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -14,7 +14,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: # Fetch changes to previous commit - required for 'only_changed' in Prettier action fetch-depth: 0 diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index ddd60750d1..0102cf316a 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -16,7 +16,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index c971be522f..1f58969202 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -21,7 +21,7 @@ jobs: run: npm install semver@7.3.5 fs-extra@10.0.0 @manypkg/get-packages@1.1.1 - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: path: backstage # 'v' prefix is added here for the tag, we keep it out of the manifest logic @@ -29,7 +29,7 @@ jobs: # Checkout backstage/versions into /backstage/versions, which is where store the output - name: Checkout versions - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: repository: backstage/versions path: backstage/versions diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index 9d031812ea..01e6ee6dfc 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -16,7 +16,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 2ea3fcd5a1..cb5eaa1737 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -16,7 +16,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index e6b274c395..4678c36907 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -29,7 +29,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Monitor and Synchronize Snyk Policies uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master with: diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 884bcf3d95..a7f8d3674e 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 20000 fetch-tags: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 5ab208590a..6df4eeed4e 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -31,7 +31,7 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: setup-node uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -89,7 +89,7 @@ jobs: egress-policy: audit - name: Checkout git repo - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Render Compose File run: | # update image after the build above diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 75415f7961..996153f8d3 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -24,7 +24,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Use Node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 0c30218d72..bcac7e947c 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -47,7 +47,7 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index f784608493..5b213f2c14 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -16,7 +16,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 # Vale does not support file excludes, so we use the script to generate a list of files instead # The action also does not allow args or a local config file to be passed in, so the files array diff --git a/.github/workflows/verify_e2e-kubernetes.yml b/.github/workflows/verify_e2e-kubernetes.yml index 9763cbd085..77d2c3bdc1 100644 --- a/.github/workflows/verify_e2e-kubernetes.yml +++ b/.github/workflows/verify_e2e-kubernetes.yml @@ -26,7 +26,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index b9229263ae..f8583ac178 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -45,7 +45,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Configure Git run: | diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 31d9d8b06a..2ed566e36b 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -34,7 +34,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.9' diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index 064baf700e..e2a789d666 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -42,7 +42,7 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Configure Git run: | diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index 152fe9f185..ba5d177cc4 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -19,7 +19,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install Fossa run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash" diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 48d7da218d..256b00b160 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml index 9728bf57c6..9986ae051e 100644 --- a/.github/workflows/verify_microsite_accessibility.yml +++ b/.github/workflows/verify_microsite_accessibility.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Use Node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 9a59b7b071..1859c0b8bc 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -32,7 +32,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 0 # Required to retrieve git history diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index cd7a9b62ae..5c4bed2d11 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -33,7 +33,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 From ad5612613f00d5ea256b4a5241ad08f6c06ac68a Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 27 May 2024 15:30:05 +0200 Subject: [PATCH 097/118] Update .changeset/warm-bees-hope.md Co-authored-by: Vincenzo Scamporlino Signed-off-by: Camila Belo --- .changeset/warm-bees-hope.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/warm-bees-hope.md b/.changeset/warm-bees-hope.md index 78b77cf5fb..2a3de7fda7 100644 --- a/.changeset/warm-bees-hope.md +++ b/.changeset/warm-bees-hope.md @@ -2,4 +2,4 @@ '@backstage/backend-plugin-api': patch --- -We renamed `BackendPluginConfig`, `BackendModuleConfig`, and `ExtensionPointConfig` respectively to `CreateBackendPluginOptions`, `CreateBackendModuleOptions`, and `CreateExtensionPointOptions` in order to standardize frontend and backend factories signatures. +Renamed `BackendPluginConfig`, `BackendModuleConfig`, and `ExtensionPointConfig` respectively to `CreateBackendPluginOptions`, `CreateBackendModuleOptions`, and `CreateExtensionPointOptions` to standardize frontend and backend factories signatures. From c0cd34912703c5ce757886bb79dbe41c150007cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 13:38:33 +0000 Subject: [PATCH 098/118] chore(deps): update dependency @changesets/cli to v2.27.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index ec0e25f926..ee0c67c9d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7835,9 +7835,9 @@ __metadata: languageName: node linkType: hard -"@changesets/apply-release-plan@npm:^7.0.0": - version: 7.0.0 - resolution: "@changesets/apply-release-plan@npm:7.0.0" +"@changesets/apply-release-plan@npm:^7.0.1": + version: 7.0.1 + resolution: "@changesets/apply-release-plan@npm:7.0.1" dependencies: "@babel/runtime": ^7.20.1 "@changesets/config": ^3.0.0 @@ -7852,7 +7852,7 @@ __metadata: prettier: ^2.7.1 resolve-from: ^5.0.0 semver: ^7.5.3 - checksum: ad83f89a3d46cd5249fa960cb0324114532bd5f25e74466d181afd6661273824859d038a12ba587a5e044f9169810e4a6febbb61e23c3819b3b28c00176a8bdf + checksum: 44a2686d3dc3ee569f23862a6c3da5c247987b320ddaf64be6c2096bb486b3da620c9336164f73e30e6272149e435988b46776508b81f328e1d66e885a8264cc languageName: node linkType: hard @@ -7894,11 +7894,11 @@ __metadata: linkType: hard "@changesets/cli@npm:^2.14.0": - version: 2.27.1 - resolution: "@changesets/cli@npm:2.27.1" + version: 2.27.3 + resolution: "@changesets/cli@npm:2.27.3" dependencies: "@babel/runtime": ^7.20.1 - "@changesets/apply-release-plan": ^7.0.0 + "@changesets/apply-release-plan": ^7.0.1 "@changesets/assemble-release-plan": ^6.0.0 "@changesets/changelog-git": ^0.2.0 "@changesets/config": ^3.0.0 @@ -7910,7 +7910,7 @@ __metadata: "@changesets/pre": ^2.0.0 "@changesets/read": ^0.6.0 "@changesets/types": ^6.0.0 - "@changesets/write": ^0.3.0 + "@changesets/write": ^0.3.1 "@manypkg/get-packages": ^1.1.3 "@types/semver": ^7.5.0 ansi-colors: ^4.1.3 @@ -7931,7 +7931,7 @@ __metadata: tty-table: ^4.1.5 bin: changeset: bin.js - checksum: 0d030dec7e0ef28626082a257d57f46cdf65edb65a95f5a3511a9d298ca052388d8ab7f9a714943864eddc59148c4afb0b802a9c75b5bea45aade4c0dc7a5fa6 + checksum: e3b0bb3a123f71701f3b76e80104968fdf99a7403b97860631ed6f98f7ba0df5d9fb56767191fa46148372a0b35d44f7883decb2b70f60d5fa2716692332c0fd languageName: node linkType: hard @@ -8071,16 +8071,16 @@ __metadata: languageName: node linkType: hard -"@changesets/write@npm:^0.3.0": - version: 0.3.0 - resolution: "@changesets/write@npm:0.3.0" +"@changesets/write@npm:^0.3.1": + version: 0.3.1 + resolution: "@changesets/write@npm:0.3.1" dependencies: "@babel/runtime": ^7.20.1 "@changesets/types": ^6.0.0 fs-extra: ^7.0.1 human-id: ^1.0.2 prettier: ^2.7.1 - checksum: 37588eb3ef2af15b3ea09d46864c994780619d20b791ea5b654801a035a3a12540c7f953e6e4f36731678615edc6d1c32f8fe174d599d3e6ce2d68263865788b + checksum: 6df0447e05ededbab71f36e6ad23aa77cf06eb6adda7a8b8e7fb9d6bd5bc93acceb916d55b2a37cb7e93fb05d39a236a0dd7ade5243aae4772885081101d4784 languageName: node linkType: hard From c87dea17c0fcc5c6b81606262ae070982201e316 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 27 May 2024 15:58:06 +0200 Subject: [PATCH 099/118] chore: fix formData should be undefined Signed-off-by: blam --- plugins/scaffolder-react/src/extensions/rjsf.ts | 2 +- .../components/fields/MultiEntityPicker/MultiEntityPicker.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-react/src/extensions/rjsf.ts b/plugins/scaffolder-react/src/extensions/rjsf.ts index b81f06758f..b90caabdb0 100644 --- a/plugins/scaffolder-react/src/extensions/rjsf.ts +++ b/plugins/scaffolder-react/src/extensions/rjsf.ts @@ -64,7 +64,7 @@ export interface ScaffolderRJSFFieldProps< /** The tree of unique ids for every child field */ idSchema: IdSchema; /** The data for this field */ - formData: T; + formData?: T; /** The tree of errors for this field and its children */ errorSchema?: ErrorSchema; /** The field change event handler; called with the updated form data and an optional `ErrorSchema` */ diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index f8fc831976..4ea26b4414 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -115,7 +115,7 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { } // We need to check against formData here as that's the previous value for this field. - if (formData.includes(ref) || allowArbitraryValues) { + if (formData?.includes(ref) || allowArbitraryValues) { return entityRef; } } @@ -173,7 +173,7 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { required={required} InputProps={{ ...params.InputProps, - required: formData.length === 0 && required, + required: formData?.length === 0 && required, }} /> )} From dfc389a04a57f5722c610c9b19071f4474e0892e Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 27 May 2024 16:06:29 +0200 Subject: [PATCH 100/118] chore: updating api-reports Signed-off-by: blam --- plugins/scaffolder-react/api-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-react/api-report.md b/plugins/scaffolder-react/api-report.md index 311f4116b3..850c30ced8 100644 --- a/plugins/scaffolder-react/api-report.md +++ b/plugins/scaffolder-react/api-report.md @@ -304,7 +304,7 @@ export interface ScaffolderRJSFFieldProps< disabled: boolean; errorSchema?: ErrorSchema; formContext?: F; - formData: T; + formData?: T; hideError?: boolean; idPrefix?: string; idSchema: IdSchema; From ac34e21771ed362ab81dadd12b29e2453f915dc4 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Mon, 27 May 2024 16:22:57 +0200 Subject: [PATCH 101/118] Update soft-flies-live.md Signed-off-by: Ben Lambert --- .changeset/soft-flies-live.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/soft-flies-live.md b/.changeset/soft-flies-live.md index 2de68f12ee..b331269647 100644 --- a/.changeset/soft-flies-live.md +++ b/.changeset/soft-flies-live.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder-backend-module-gitlab': minor +'@backstage/plugin-scaffolder-backend-module-gitlab': patch --- -Add new Scaffolder action to trigger GitLab pipelines. +Add new `gitlab:pipeline:trigger` action to trigger GitLab pipelines. From e5049d33363b00e17de39932eaa9c1cff6e56fa3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 14:30:36 +0000 Subject: [PATCH 102/118] chore(deps): update dependency @types/lodash to v4.17.4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ee0c67c9d6..79803dc908 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17605,9 +17605,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151": - version: 4.17.1 - resolution: "@types/lodash@npm:4.17.1" - checksum: 01984d5b44c09ef45258f8ac6d0cf926900624064722d51a020ba179e5d4a293da0068fb278d87dc695586afe7ebd3362ec57f5c0e7c4f6c1fab9d04a80e77f5 + version: 4.17.4 + resolution: "@types/lodash@npm:4.17.4" + checksum: 268e652fd52d49189f155bc89b49bd4535aa44f0b6b0ed9ce7e50318307bda58147c49539d2047f39ca37cf5b5ea38dfb801d0dbcdbc8b019c95c1afc346b05a languageName: node linkType: hard From c1eeac9180fe8fd44eb9462c205b5b4a044352bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 14:31:38 +0000 Subject: [PATCH 103/118] chore(deps): update dependency lint-staged to v15.2.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 179 +++++++++++++++++++++++++++--------------------------- 1 file changed, 91 insertions(+), 88 deletions(-) diff --git a/yarn.lock b/yarn.lock index ee0c67c9d6..3b491e5899 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20986,12 +20986,12 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.2, braces@npm:~3.0.2": - version: 3.0.2 - resolution: "braces@npm:3.0.2" +"braces@npm:^3.0.2, braces@npm:^3.0.3, braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" dependencies: - fill-range: ^7.0.1 - checksum: e2a8e769a863f3d4ee887b5fe21f63193a891c68b612ddb4b68d82d1b5f3ff9073af066c343e9867a393fe4c2555dcb33e89b937195feb9c1613d259edfcd459 + fill-range: ^7.1.1 + checksum: b95aa0b3bd909f6cd1720ffcf031aeaf46154dd88b4da01f9a1d3f7ea866a79eba76a6d01cbc3c422b2ee5cdc39a4f02491058d5df0d7bf6e6a162a832df1f69 languageName: node linkType: hard @@ -21547,13 +21547,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:5.3.0": - version: 5.3.0 - resolution: "chalk@npm:5.3.0" - checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 - languageName: node - linkType: hard - "chalk@npm:^3.0.0": version: 3.0.0 resolution: "chalk@npm:3.0.0" @@ -21564,6 +21557,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:~5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -22177,17 +22177,10 @@ __metadata: languageName: node linkType: hard -"commander@npm:*, commander@npm:^12.0.0": - version: 12.0.0 - resolution: "commander@npm:12.0.0" - checksum: bce9e243dc008baba6b8d923f95b251ad115e6e7551a15838d7568abebcca0fc832da1800cf37caf37852f35ce4b7fb794ba7a4824b88c5adb1395f9268642df - languageName: node - linkType: hard - -"commander@npm:11.1.0, commander@npm:^11.0.0": - version: 11.1.0 - resolution: "commander@npm:11.1.0" - checksum: fd1a8557c6b5b622c89ecdfde703242ab7db3b628ea5d1755784c79b8e7cb0d74d65b4a262289b533359cd58e1bfc0bf50245dfbcd2954682a6f367c828b79ef +"commander@npm:*, commander@npm:^12.0.0, commander@npm:~12.1.0": + version: 12.1.0 + resolution: "commander@npm:12.1.0" + checksum: 68e9818b00fc1ed9cdab9eb16905551c2b768a317ae69a5e3c43924c2b20ac9bb65b27e1cab36aeda7b6496376d4da908996ba2c0b5d79463e0fb1e77935d514 languageName: node linkType: hard @@ -22212,6 +22205,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^11.0.0": + version: 11.1.0 + resolution: "commander@npm:11.1.0" + checksum: fd1a8557c6b5b622c89ecdfde703242ab7db3b628ea5d1755784c79b8e7cb0d74d65b4a262289b533359cd58e1bfc0bf50245dfbcd2954682a6f367c828b79ef + languageName: node + linkType: hard + "commander@npm:^2.19.0, commander@npm:^2.20.0, commander@npm:^2.7.1": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -23526,7 +23526,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.3.4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:4.3.4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -25828,23 +25828,6 @@ __metadata: languageName: unknown linkType: soft -"execa@npm:8.0.1": - version: 8.0.1 - resolution: "execa@npm:8.0.1" - dependencies: - cross-spawn: ^7.0.3 - get-stream: ^8.0.1 - human-signals: ^5.0.0 - is-stream: ^3.0.0 - merge-stream: ^2.0.0 - npm-run-path: ^5.1.0 - onetime: ^6.0.0 - signal-exit: ^4.1.0 - strip-final-newline: ^3.0.0 - checksum: cac1bf86589d1d9b73bdc5dda65c52012d1a9619c44c526891956745f7b366ca2603d29fe3f7460bacc2b48c6eab5d6a4f7afe0534b31473d3708d1265545e1f - languageName: node - linkType: hard - "execa@npm:^1.0.0": version: 1.0.0 resolution: "execa@npm:1.0.0" @@ -25877,6 +25860,23 @@ __metadata: languageName: node linkType: hard +"execa@npm:~8.0.1": + version: 8.0.1 + resolution: "execa@npm:8.0.1" + dependencies: + cross-spawn: ^7.0.3 + get-stream: ^8.0.1 + human-signals: ^5.0.0 + is-stream: ^3.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^5.1.0 + onetime: ^6.0.0 + signal-exit: ^4.1.0 + strip-final-newline: ^3.0.0 + checksum: cac1bf86589d1d9b73bdc5dda65c52012d1a9619c44c526891956745f7b366ca2603d29fe3f7460bacc2b48c6eab5d6a4f7afe0534b31473d3708d1265545e1f + languageName: node + linkType: hard + "exit-hook@npm:^2.2.1": version: 2.2.1 resolution: "exit-hook@npm:2.2.1" @@ -26390,12 +26390,12 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^7.0.1": - version: 7.0.1 - resolution: "fill-range@npm:7.0.1" +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" dependencies: to-regex-range: ^5.0.1 - checksum: cc283f4e65b504259e64fd969bcf4def4eb08d85565e906b7d36516e87819db52029a76b6363d0f02d0d532f0033c9603b9e2d943d56ee3b0d4f7ad3328ff917 + checksum: b4abfbca3839a3d55e4ae5ec62e131e2e356bf4859ce8480c64c4876100f4df292a63e5bb1618e1d7460282ca2b305653064f01654474aa35c68000980f17798 languageName: node linkType: hard @@ -31319,13 +31319,6 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:3.0.0": - version: 3.0.0 - resolution: "lilconfig@npm:3.0.0" - checksum: a155f1cd24d324ab20dd6974db9ebcf3fb6f2b60175f7c052d917ff8a746b590bc1ee550f6fc3cb1e8716c8b58304e22fe2193febebc0cf16fa86d85e6f896c5 - languageName: node - linkType: hard - "lilconfig@npm:^2.0.3": version: 2.1.0 resolution: "lilconfig@npm:2.1.0" @@ -31333,6 +31326,13 @@ __metadata: languageName: node linkType: hard +"lilconfig@npm:~3.1.1": + version: 3.1.1 + resolution: "lilconfig@npm:3.1.1" + checksum: dc8a4f4afde3f0fac6bd36163cc4777a577a90759b8ef1d0d766b19ccf121f723aa79924f32af5b954f3965268215e046d0f237c41c76e5ef01d4e6d1208a15e + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -31367,22 +31367,22 @@ __metadata: linkType: hard "lint-staged@npm:^15.0.0": - version: 15.2.2 - resolution: "lint-staged@npm:15.2.2" + version: 15.2.5 + resolution: "lint-staged@npm:15.2.5" dependencies: - chalk: 5.3.0 - commander: 11.1.0 - debug: 4.3.4 - execa: 8.0.1 - lilconfig: 3.0.0 - listr2: 8.0.1 - micromatch: 4.0.5 - pidtree: 0.6.0 - string-argv: 0.3.2 - yaml: 2.3.4 + chalk: ~5.3.0 + commander: ~12.1.0 + debug: ~4.3.4 + execa: ~8.0.1 + lilconfig: ~3.1.1 + listr2: ~8.2.1 + micromatch: ~4.0.7 + pidtree: ~0.6.0 + string-argv: ~0.3.2 + yaml: ~2.4.2 bin: lint-staged: bin/lint-staged.js - checksum: 031718ad3f839475fb1d41bda34bab4330f25814175808169daa2686ff026e5a667a25c95fdf3cd46dac72f9af2c98852565bb62d920992f5e2d3f730c279760 + checksum: 3025868d965eb401a5ebd903abd70cfebb8dbeb41eea1020c316f9c8c79083ea203f6cef95d32bfa8c9ec5486392b4ed08632ace6a5347b69cf238ba00e178f0 languageName: node linkType: hard @@ -31393,17 +31393,17 @@ __metadata: languageName: node linkType: hard -"listr2@npm:8.0.1": - version: 8.0.1 - resolution: "listr2@npm:8.0.1" +"listr2@npm:~8.2.1": + version: 8.2.1 + resolution: "listr2@npm:8.2.1" dependencies: cli-truncate: ^4.0.0 colorette: ^2.0.20 eventemitter3: ^5.0.1 log-update: ^6.0.0 - rfdc: ^1.3.0 + rfdc: ^1.3.1 wrap-ansi: ^9.0.0 - checksum: 4dfeabfa037b3981d0edbf30789971ba727ba4cfcc13051ceaff7a1b3d26509ef2d946015c65c600b0775ec9d1ef58a81937d94c9c03de464b654f429cc7c3ed + checksum: a37c032850fc01f45cf6144f2b66d0c56a596b708de1acbd52e7c396a2eb188d027ad132c93a0ad946d7932a581dfcfc2e4318bb301926b01877cb4903d09fbd languageName: node linkType: hard @@ -32894,7 +32894,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:4.0.5, micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": +"micromatch@npm:4.0.5": version: 4.0.5 resolution: "micromatch@npm:4.0.5" dependencies: @@ -32904,6 +32904,16 @@ __metadata: languageName: node linkType: hard +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:~4.0.7": + version: 4.0.7 + resolution: "micromatch@npm:4.0.7" + dependencies: + braces: ^3.0.3 + picomatch: ^2.3.1 + checksum: 3cde047d70ad80cf60c787b77198d680db3b8c25b23feb01de5e2652205d9c19f43bd81882f69a0fd1f0cde6a7a122d774998aad3271ddb1b8accf8a0f480cf7 + languageName: node + linkType: hard + "miller-rabin@npm:^4.0.0": version: 4.0.1 resolution: "miller-rabin@npm:4.0.1" @@ -35775,7 +35785,7 @@ __metadata: languageName: node linkType: hard -"pidtree@npm:0.6.0": +"pidtree@npm:~0.6.0": version: 0.6.0 resolution: "pidtree@npm:0.6.0" bin: @@ -38672,10 +38682,10 @@ __metadata: languageName: node linkType: hard -"rfdc@npm:^1.3.0": - version: 1.3.0 - resolution: "rfdc@npm:1.3.0" - checksum: fb2ba8512e43519983b4c61bd3fa77c0f410eff6bae68b08614437bc3f35f91362215f7b4a73cbda6f67330b5746ce07db5dd9850ad3edc91271ad6deea0df32 +"rfdc@npm:^1.3.1": + version: 1.3.1 + resolution: "rfdc@npm:1.3.1" + checksum: d5d1e930aeac7e0e0a485f97db1356e388bdbeff34906d206fe524dd5ada76e95f186944d2e68307183fdc39a54928d4426bbb6734851692cfe9195efba58b79 languageName: node linkType: hard @@ -40451,7 +40461,7 @@ __metadata: languageName: node linkType: hard -"string-argv@npm:0.3.2, string-argv@npm:~0.3.1": +"string-argv@npm:~0.3.1, string-argv@npm:~0.3.2": version: 0.3.2 resolution: "string-argv@npm:0.3.2" checksum: 8703ad3f3db0b2641ed2adbb15cf24d3945070d9a751f9e74a924966db9f325ac755169007233e8985a39a6a292f14d4fee20482989b89b96e473c4221508a0f @@ -43976,13 +43986,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:2.3.4": - version: 2.3.4 - resolution: "yaml@npm:2.3.4" - checksum: e6d1dae1c6383bcc8ba11796eef3b8c02d5082911c6723efeeb5ba50fc8e881df18d645e64de68e421b577296000bea9c75d6d9097c2f6699da3ae0406c030d8 - languageName: node - linkType: hard - "yaml@npm:^1.10.0, yaml@npm:^1.10.2, yaml@npm:^1.7.2": version: 1.10.2 resolution: "yaml@npm:1.10.2" @@ -43990,12 +43993,12 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3": - version: 2.4.1 - resolution: "yaml@npm:2.4.1" +"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3, yaml@npm:~2.4.2": + version: 2.4.2 + resolution: "yaml@npm:2.4.2" bin: yaml: bin.mjs - checksum: 4c391d07a5d5e935e058babb71026c9cdc9a6fd889e35dd91b53cfb0a12691b67c6c5c740858e71345fef18cd9c13c554a6dda9196f59820d769d94041badb0b + checksum: 90dda4485de04367251face9abb5c36927c94e44078f4e958e6468a07e74e7e92f89be20fc49860b6268c51ee5a5fc79ef89197d3f874bf24ef8921cc4ba9013 languageName: node linkType: hard From e17d4a384105faec9f2a5e44951ad624510a5e0d Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Mon, 27 May 2024 16:51:24 +0200 Subject: [PATCH 104/118] Update .changeset/empty-tables-ring.md Co-authored-by: Vincenzo Scamporlino Signed-off-by: Alex Eftimie --- .changeset/empty-tables-ring.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/empty-tables-ring.md b/.changeset/empty-tables-ring.md index 1b52801ae0..c83a987228 100644 --- a/.changeset/empty-tables-ring.md +++ b/.changeset/empty-tables-ring.md @@ -1,8 +1,8 @@ --- '@backstage/plugin-kubernetes-react': minor '@backstage/plugin-catalog-import': minor -'@backstage/plugin-kubernetes': minor -'@backstage/plugin-search': minor +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-search': patch --- Migrate from identityApi to fetchApi in frontend plugins. From 75dcd7e0a96b97ca91f6eb51d4ea51aab13e1086 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 27 May 2024 16:58:57 +0200 Subject: [PATCH 105/118] chore: added changeset Signed-off-by: blam --- .changeset/spicy-brooms-hang.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/spicy-brooms-hang.md diff --git a/.changeset/spicy-brooms-hang.md b/.changeset/spicy-brooms-hang.md new file mode 100644 index 0000000000..23f7da309b --- /dev/null +++ b/.changeset/spicy-brooms-hang.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +--- + +Fixing bug in `formData` type as it should be `optional` as it's possibly undefined From 55e802ebbda7b277c9b2866279a859aa48283f3c Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 11:15:09 -0400 Subject: [PATCH 106/118] fix(openapi-tooling): breaking changes check Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 5f456a9b0b..e2faa6cd37 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -39,7 +39,7 @@ jobs: - name: breaking changes check run: | - yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md + yarn backstage-repo-tools repo schema openapi diff --since origin/${{ github.base_ref }} > comment.md - name: Upload Rendered Comment as Artifact uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 From cb557001da59c1c4afff508f1bcb0a52b33207dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 15:16:20 +0000 Subject: [PATCH 107/118] chore(deps): update dependency nodemon to v3.1.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 79803dc908..09c480ef59 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34067,8 +34067,8 @@ __metadata: linkType: hard "nodemon@npm:^3.0.1": - version: 3.1.0 - resolution: "nodemon@npm:3.1.0" + version: 3.1.1 + resolution: "nodemon@npm:3.1.1" dependencies: chokidar: ^3.5.2 debug: ^4 @@ -34082,7 +34082,7 @@ __metadata: undefsafe: ^2.0.5 bin: nodemon: bin/nodemon.js - checksum: 0b721f66ee60d9bf092f6101965bc65769698fa2921d0283d90bbf3f0906aa4f3ac77316682375bd7f09c91679fddb131aa39f9fc839fea57061bbc8e81b60e3 + checksum: 43ed211d3a1eb267444265454c0dd306177fcef119c8c095b737d843648e7b51f10c033c262ec10a09df60aa2f237904fa659a96d0562541c55b87c3f5ba77ff languageName: node linkType: hard From 5c093f297d856189042feb7856a03a57fe1b182f Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 20:39:11 -0400 Subject: [PATCH 108/118] fix(ci): workflow files aren't saving artifacts as expected Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 11 +++-------- .github/workflows/uffizzi-build.yml | 13 ++++--------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 5f456a9b0b..0f51dc152f 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -45,14 +45,9 @@ jobs: uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec - path: comment.md + path: | + comment.md + ${{ github.event_path }} retention-days: 2 overwrite: true - - name: Upload PR Event as Artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - with: - name: preview-spec - path: ${{ github.event_path }} - retention-days: 2 - overwrite: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 5ab208590a..3542858273 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -97,18 +97,13 @@ jobs: kustomize edit set image backstage=${{ needs.build-backstage.outputs.tags }} kustomize build . > manifests.rendered.yml cat manifests.rendered.yml - - name: Upload Rendered Manifests File as Artifact + - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec - path: ./.github/uffizzi/k8s/manifests/manifests.rendered.yml - retention-days: 2 - overwrite: true - - name: Upload PR Event as Artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - with: - name: preview-spec - path: ${{ github.event_path }} + path: | + ./.github/uffizzi/k8s/manifests/manifests.rendered.yml + ${{ github.event_path }} retention-days: 2 overwrite: true From 0413878007aec63331ff487712d44d51a03e6a97 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 20:48:29 -0400 Subject: [PATCH 109/118] workflows: adjust name of workflow Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 0f51dc152f..ddf8bde41d 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -41,7 +41,7 @@ jobs: run: | yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md - - name: Upload Rendered Comment as Artifact + - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec From 50a608a7d90e0ac4633b0994664cd0bd68107fbc Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 21:12:24 -0400 Subject: [PATCH 110/118] workflows: clone events.json locally Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 6 +++++- .github/workflows/uffizzi-build.yml | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index ddf8bde41d..4a470290cd 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -41,13 +41,17 @@ jobs: run: | yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md + - name: clone github events.json to local path + run: | + cat ${{ github.event_path }} > events.json + - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec path: | comment.md - ${{ github.event_path }} + events.json retention-days: 2 overwrite: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 3542858273..b981b46e0a 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -97,13 +97,18 @@ jobs: kustomize edit set image backstage=${{ needs.build-backstage.outputs.tags }} kustomize build . > manifests.rendered.yml cat manifests.rendered.yml + + - name: clone github events.json locally + run: | + cat ${{ github.event_path }} > events.json + - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec path: | ./.github/uffizzi/k8s/manifests/manifests.rendered.yml - ${{ github.event_path }} + events.json retention-days: 2 overwrite: true From 89e2ddf2158b2d7fcf591281d668952dbc97ba6b Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 22:29:18 -0400 Subject: [PATCH 111/118] workflow: stage all files into top level zip dir Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 6 +++--- .github/workflows/uffizzi-build.yml | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 4a470290cd..12fda469ee 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -41,9 +41,9 @@ jobs: run: | yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md - - name: clone github events.json to local path + - name: clone artifacts to current directory run: | - cat ${{ github.event_path }} > events.json + cat ${{ github.event_path }} > event.json - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 @@ -51,7 +51,7 @@ jobs: name: preview-spec path: | comment.md - events.json + event.json retention-days: 2 overwrite: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index b981b46e0a..95a4fa2e11 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -98,17 +98,18 @@ jobs: kustomize build . > manifests.rendered.yml cat manifests.rendered.yml - - name: clone github events.json locally + - name: clone artifacts into current directory run: | - cat ${{ github.event_path }} > events.json + cat ${{ github.event_path }} > event.json + cp ./.github/uffizzi/k8s/manifests/manifests.rendered.yml manifests.rendered.yml - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec path: | - ./.github/uffizzi/k8s/manifests/manifests.rendered.yml - events.json + manifests.rendered.yml + event.json retention-days: 2 overwrite: true From 18e6e7a14a65531009d0a019d6c4481948126797 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 22:38:49 -0400 Subject: [PATCH 112/118] fix prettier Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 12fda469ee..711842a02c 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -54,4 +54,3 @@ jobs: event.json retention-days: 2 overwrite: true - From 9b7aacf7c08df0308bf42e86c2751b039a356441 Mon Sep 17 00:00:00 2001 From: Dmitriy Lazarev Date: Tue, 28 May 2024 13:12:04 +0400 Subject: [PATCH 113/118] Add graphql-plugin to OSS plugins list Signed-off-by: Dmitriy Lazarev --- microsite/data/plugins/graphql-catalog.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 microsite/data/plugins/graphql-catalog.yaml diff --git a/microsite/data/plugins/graphql-catalog.yaml b/microsite/data/plugins/graphql-catalog.yaml new file mode 100644 index 0000000000..9255f475e7 --- /dev/null +++ b/microsite/data/plugins/graphql-catalog.yaml @@ -0,0 +1,14 @@ +--- +title: GraphQL Catalog +author: Frontside Software +authorUrl: https://frontside.com/ +category: Discovery +description: Adds the GraphQL Endpoint to Backstage Catalog as a plugin. +documentation: https://github.com/thefrontside/playhouse/blob/main/plugins/graphql-backend/README.md +iconUrl: https://raw.githubusercontent.com/thefrontside/frontside.com/production/legacy/src/img/frontside-logo.png +npmPackageName: '@frontside/backstage-plugin-graphql-backend' +tags: + - graphql + - catalog + - graphql-catalog +addedDate: '2024-05-01' From 73ca211b4b2fc0e51e726d23def5b3927e65a4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Tue, 28 May 2024 13:28:14 +0200 Subject: [PATCH 114/118] Fix typo in 03-services.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jonas Müller --- docs/backend-system/architecture/03-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index 573f6612c2..59aa48e0eb 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -111,7 +111,7 @@ There are only two possible scopes for services, `'plugin'` and `'root'`. ## Root Scoped Services -If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factory for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin. +If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factors for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin. There is a limitation in the usage of root scoped services, which is that their implementation can only depend on other root scoped services. Plugin scoped services on the other hand can depend on both root and plugin scoped services. Because of this limitation, one of the main reasons to define a root scoped services is to make it possible for other root scoped services to depend on it. From 6163500c38bfdb51ad9e49740e374a8b96ffd8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Tue, 28 May 2024 13:34:58 +0200 Subject: [PATCH 115/118] Fix typo in 03-services.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jonas Müller --- docs/backend-system/architecture/03-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index 59aa48e0eb..dbb316e5a1 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -111,7 +111,7 @@ There are only two possible scopes for services, `'plugin'` and `'root'`. ## Root Scoped Services -If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factors for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin. +If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factor for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin. There is a limitation in the usage of root scoped services, which is that their implementation can only depend on other root scoped services. Plugin scoped services on the other hand can depend on both root and plugin scoped services. Because of this limitation, one of the main reasons to define a root scoped services is to make it possible for other root scoped services to depend on it. From b6e5a34bfd5a6a27bf54e5c35ae43f5c771e04b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Tue, 28 May 2024 13:46:56 +0200 Subject: [PATCH 116/118] Fix typo in 03-services.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jonas Müller --- docs/backend-system/architecture/03-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index dbb316e5a1..93326eb0d9 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -157,7 +157,7 @@ export const fooServiceFactory = createServiceFactory({ }); ``` -Whatever value is returned by the `createRootContext` function will shared and passed as the second argument to each invocation of the `factory` function. That way you can create a shared context that is used in the creation of each plugin instance. Unlike the `factory` function, the `createRootContext` function will only receive root scoped services as its dependencies, but just like the `factory` function, it can also be `async`. +Whatever value is returned by the `createRootContext` function will be shared and passed as the second argument to each invocation of the `factory` function. That way you can create a shared context that is used in the creation of each plugin instance. Unlike the `factory` function, the `createRootContext` function will only receive root scoped services as its dependencies, but just like the `factory` function, it can also be `async`. ## Default Service Factories From 61232f63de64406499383ee66708dd191c7b488e Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 28 May 2024 14:50:15 +0200 Subject: [PATCH 117/118] docs: fix proxy typo Signed-off-by: Vincenzo Scamporlino --- docs/tutorials/using-backstage-proxy-within-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md index 3002eb1aff..1abfe9e5b6 100644 --- a/docs/tutorials/using-backstage-proxy-within-plugin.md +++ b/docs/tutorials/using-backstage-proxy-within-plugin.md @@ -125,7 +125,7 @@ export class MyAwesomeApiClient implements MyAwesomeApi { private async fetch(input: string, init?: RequestInit): Promise { // As configured previously for the backend proxy - const proxyUri = '${await this.discoveryApi.getBaseUrl('proxy')}/'; + const proxyUri = `${await this.discoveryApi.getBaseUrl('proxy')}/`; const resp = await fetch(`${proxyUri}${input}`, init); if (!resp.ok) throw new Error(resp); From 77da22e67fd95673f26f07ba717bae2d9de87dbc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 May 2024 13:43:01 +0000 Subject: [PATCH 118/118] Version Packages (next) --- .changeset/create-app-1716903719.md | 5 + .changeset/pre.json | 30 + docs/releases/v1.28.0-next.1-changelog.md | 1014 ++++++++++++ package.json | 2 +- packages/app-next/CHANGELOG.md | 16 + packages/app-next/package.json | 2 +- packages/app/CHANGELOG.md | 16 + packages/app/package.json | 2 +- packages/backend-app-api/CHANGELOG.md | 15 + packages/backend-app-api/package.json | 2 +- packages/backend-common/CHANGELOG.md | 14 + packages/backend-common/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 16 + packages/backend-defaults/package.json | 2 +- .../CHANGELOG.md | 19 + .../package.json | 2 +- packages/backend-legacy/CHANGELOG.md | 37 + packages/backend-legacy/package.json | 2 +- packages/backend-plugin-api/CHANGELOG.md | 9 + packages/backend-plugin-api/package.json | 2 +- packages/backend-tasks/CHANGELOG.md | 9 + packages/backend-tasks/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 16 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 34 + packages/backend/package.json | 2 +- packages/cli/CHANGELOG.md | 10 + packages/cli/package.json | 2 +- packages/create-app/CHANGELOG.md | 6 + packages/create-app/package.json | 2 +- packages/repo-tools/CHANGELOG.md | 10 + packages/repo-tools/package.json | 2 +- packages/techdocs-cli/CHANGELOG.md | 8 + packages/techdocs-cli/package.json | 2 +- plugins/app-backend/CHANGELOG.md | 10 + plugins/app-backend/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 24 + plugins/auth-backend/package.json | 2 +- plugins/auth-node/CHANGELOG.md | 8 + plugins/auth-node/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 11 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 10 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../catalog-backend-module-gcp/CHANGELOG.md | 11 + .../catalog-backend-module-gcp/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 13 + .../catalog-backend-module-ldap/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 14 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 11 + plugins/catalog-import/package.json | 2 +- plugins/catalog/CHANGELOG.md | 15 + plugins/catalog/package.json | 2 +- plugins/devtools-backend/CHANGELOG.md | 10 + plugins/devtools-backend/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/events-backend/CHANGELOG.md | 9 + plugins/events-backend/package.json | 2 +- .../example-todo-list-backend/CHANGELOG.md | 9 + .../example-todo-list-backend/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 17 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-cluster/CHANGELOG.md | 9 + plugins/kubernetes-cluster/package.json | 2 +- plugins/kubernetes-common/CHANGELOG.md | 6 + plugins/kubernetes-common/package.json | 2 +- plugins/kubernetes-node/CHANGELOG.md | 8 + plugins/kubernetes-node/package.json | 2 +- plugins/kubernetes-react/CHANGELOG.md | 12 + plugins/kubernetes-react/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 10 + plugins/kubernetes/package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- plugins/notifications-backend/CHANGELOG.md | 16 + plugins/notifications-backend/package.json | 2 +- plugins/notifications-node/CHANGELOG.md | 14 + plugins/notifications-node/package.json | 2 +- plugins/notifications/CHANGELOG.md | 6 + plugins/notifications/package.json | 2 +- plugins/permission-backend/CHANGELOG.md | 10 + plugins/permission-backend/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 10 + plugins/permission-node/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 8 + plugins/proxy-backend/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 28 + plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder-common/CHANGELOG.md | 10 + plugins/scaffolder-common/package.json | 2 +- .../scaffolder-node-test-utils/CHANGELOG.md | 9 + .../scaffolder-node-test-utils/package.json | 2 +- plugins/scaffolder-node/CHANGELOG.md | 9 + plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/CHANGELOG.md | 1413 +++++++++++++++++ plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 16 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/search-backend-module-pg/CHANGELOG.md | 10 + plugins/search-backend-module-pg/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- plugins/search-backend-node/CHANGELOG.md | 9 + plugins/search-backend-node/package.json | 2 +- plugins/search-backend/CHANGELOG.md | 12 + plugins/search-backend/package.json | 2 +- plugins/search/CHANGELOG.md | 8 + plugins/search/package.json | 2 +- plugins/signals-backend/CHANGELOG.md | 11 + plugins/signals-backend/package.json | 2 +- plugins/signals-node/CHANGELOG.md | 10 + plugins/signals-node/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 10 + plugins/techdocs-backend/package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 8 + plugins/techdocs-node/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 9 + plugins/user-settings-backend/package.json | 2 +- 178 files changed, 3552 insertions(+), 88 deletions(-) create mode 100644 .changeset/create-app-1716903719.md create mode 100644 docs/releases/v1.28.0-next.1-changelog.md diff --git a/.changeset/create-app-1716903719.md b/.changeset/create-app-1716903719.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1716903719.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/pre.json b/.changeset/pre.json index 7a0fc7ed6b..bbf21bc6a4 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -190,33 +190,63 @@ "calm-plums-wink", "cold-seas-end", "create-app-1716302437", + "create-app-1716903719", + "cyan-jobs-visit", "cyan-paws-beg", "cyan-snails-peel", "eighty-kings-dress", + "eighty-yaks-switch", "empty-spoons-tell", + "empty-tables-ring", + "famous-monkeys-count", + "forty-adults-roll", "four-adults-mix", + "friendly-keys-fold", "gentle-baboons-peel", + "gold-teachers-wink", + "great-cougars-guess", "itchy-spoons-cry", + "large-months-decide", "late-ants-impress", "late-students-live", + "little-cooks-approve", "loud-pumpkins-bow", "lovely-hats-pay", "lucky-taxis-rule", "many-moles-sing", "mean-laws-lay", + "neat-rivers-share", "new-numbers-hug", + "nice-pants-shave", + "nine-hairs-kick", "nine-ties-type", "old-trees-check", "olive-mangos-tickle", + "perfect-bikes-invite", + "polite-otters-talk", "rude-kings-press", "seven-geese-raise", + "shaggy-jokes-promise", + "six-llamas-give", "slimy-fans-raise", "smooth-gifts-nail", + "soft-flies-live", "sour-colts-juggle", + "spicy-brooms-hang", "spicy-camels-happen", + "spotty-plants-switch", + "strong-moose-work", + "stupid-tigers-bake", + "tall-lies-fetch", + "tall-pumas-teach", + "tender-seas-listen", + "thirty-plums-shout", "tiny-pandas-return", + "warm-bees-hope", + "weak-gifts-occur", "wet-crabs-guess", "wild-doors-cheat", + "wild-ears-walk", "wise-vans-sin", "wise-wasps-look", "young-camels-return" diff --git a/docs/releases/v1.28.0-next.1-changelog.md b/docs/releases/v1.28.0-next.1-changelog.md new file mode 100644 index 0000000000..cb5502738d --- /dev/null +++ b/docs/releases/v1.28.0-next.1-changelog.md @@ -0,0 +1,1014 @@ +# Release v1.28.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.28.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.28.0-next.1) + +## @backstage/backend-common@0.23.0-next.1 + +### Minor Changes + +- 02103be: Deprecated and moved over core services to `@backstage/backend-defaults` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/backend-defaults@0.3.0-next.1 + +### Minor Changes + +- 02103be: Deprecated and moved over core services to `@backstage/backend-defaults` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/backend-test-utils@0.4.0-next.1 + +### Minor Changes + +- 805cbe7: Added `TestCaches` that functions just like `TestDatabases` + +### Patch Changes + +- 9e63318: Made it possible to give access restrictions to `mockCredentials.service` +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.6.0-next.1 + +### Minor Changes + +- debcc8c: Migrate LDAP catalog module to the new backend system. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-import@0.12.0-next.1 + +### Minor Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-kubernetes-backend@0.18.0-next.1 + +### Minor Changes + +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-kubernetes-node@0.1.13-next.1 + +## @backstage/plugin-kubernetes-common@0.8.0-next.0 + +### Minor Changes + +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + +## @backstage/plugin-kubernetes-react@0.4.0-next.1 + +### Minor Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + +## @backstage/plugin-notifications-backend@0.3.0-next.1 + +### Minor Changes + +- 07a789b: adding filtering of notifications by processors + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-signals-node@0.1.5-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.1.0-next.1 + +### Minor Changes + +- 07a789b: add notification filters + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/plugin-notifications-node@0.2.0-next.1 + +### Minor Changes + +- 07a789b: add notifications filtering by processors + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-signals-node@0.1.5-next.1 + +## @backstage/backend-app-api@0.7.6-next.1 + +### Patch Changes + +- 398b82a: Add support for JWKS tokens in ExternalTokenHandler. +- 9e63318: Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/backend-dynamic-feature-service@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-backend@0.3.6-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/backend-plugin-api@0.6.19-next.1 + +### Patch Changes + +- 9e63318: Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions. +- 0665b7e: Renamed `BackendPluginConfig`, `BackendModuleConfig`, and `ExtensionPointConfig` respectively to `CreateBackendPluginOptions`, `CreateBackendModuleOptions`, and `CreateExtensionPointOptions` to standardize frontend and backend factories signatures. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/backend-tasks@0.5.24-next.1 + +### Patch Changes + +- ed473cd: Updated the `TaskScheduleDefinitionConfig` deprecated comment to point to `SchedulerServiceTaskScheduleDefinitionConfig` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/cli@0.26.7-next.1 + +### Patch Changes + +- 788eca7: Fix readme for new plugins created using cli +- c00f7ee: Fix issue with `esm` loaded dependencies being different from the `cjs` import for Vite dependencies +- Updated dependencies + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + +## @backstage/create-app@0.5.16-next.1 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/repo-tools@0.9.1-next.1 + +### Patch Changes + +- 8721a02: Add `--client-additional-properties` option to `openapi generate` command +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + +## @techdocs/cli@1.8.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + +## @backstage/plugin-app-backend@0.3.68-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-auth-backend@0.22.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.2-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.11-next.0 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.11-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.14-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.14-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.12-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.10-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.12-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.2-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-auth-node@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/plugin-catalog@1.20.1-next.1 + +### Patch Changes + +- a2d2649: Export `catalogTranslationRef` under `/alpha` + +- bcec60f: updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-catalog-backend@1.23.0-next.1 + +### Patch Changes + +- d779e3b: Added a regex test to check commit hash. If url is from git commit branch ignore the edit url. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend-module-github@0.6.2-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.17-next.1 + +### Patch Changes + +- 150fc77: Fixed an issue in `GitlabOrgDiscoveryEntityProvider` where a missing `orgEnabled` config key was throwing an error. +- f271164: Fixed an issue in `GitlabDiscoveryEntityProvider` where the fallback branch was taking precedence over the GitLab default branch. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.17-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-devtools-backend@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + +## @backstage/plugin-events-backend@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-kubernetes@0.11.11-next.1 + +### Patch Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.4.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.4.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-kubernetes-node@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + +## @backstage/plugin-notifications@0.2.2-next.1 + +### Patch Changes + +- 6d196b4: Fixes performance issue with Notifications title counter. + +## @backstage/plugin-permission-backend@0.5.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-permission-node@0.7.30-next.1 + +### Patch Changes + +- 9e63318: Ensure that service token access restrictions, when present, are taken into account +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-proxy-backend@0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/plugin-scaffolder@1.20.2-next.1 + +### Patch Changes + +- 75dcd7e: Fixing bug in `formData` type as it should be `optional` as it's possibly undefined + +- bcec60f: updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-scaffolder-backend@1.22.8-next.1 + +### Patch Changes + +- bcec60f: added the following new permissions to the scaffolder backend endpoints: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.9-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.11-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.9-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.1 + +### Patch Changes + +- 829e0ec: Add new `gitlab:pipeline:trigger` action to trigger GitLab pipelines. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.27-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-common@1.5.3-next.0 + +### Patch Changes + +- bcec60f: added the following new permissions to the scaffolder backend endpoints: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +## @backstage/plugin-scaffolder-node@0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.4.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-react@1.8.7-next.1 + +### Patch Changes + +- 75dcd7e: Fixing bug in `formData` type as it should be `optional` as it's possibly undefined +- 928cfa0: Fixed a typo ' + +## @backstage/plugin-search@1.4.12-next.1 + +### Patch Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-search-backend@1.5.10-next.1 + +### Patch Changes + +- 34dc47d: Move @backstage/repo-tools to devDependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-defaults@0.3.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-explore@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-pg@0.5.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + +## @backstage/plugin-search-backend-node@1.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/plugin-signals-backend@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-signals-node@0.1.5-next.1 + +## @backstage/plugin-signals-node@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-techdocs-backend@1.10.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + +## @backstage/plugin-techdocs-node@1.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/plugin-user-settings-backend@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## example-app@0.2.98-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.7-next.1 + - @backstage/plugin-catalog-import@0.12.0-next.1 + - @backstage/plugin-kubernetes@0.11.11-next.1 + - @backstage/plugin-search@1.4.12-next.1 + - @backstage/plugin-notifications@0.2.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder@1.20.2-next.1 + - @backstage/plugin-catalog@1.20.1-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## example-app-next@0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.7-next.1 + - @backstage/plugin-catalog-import@0.12.0-next.1 + - @backstage/plugin-kubernetes@0.11.11-next.1 + - @backstage/plugin-search@1.4.12-next.1 + - @backstage/plugin-notifications@0.2.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder@1.20.2-next.1 + - @backstage/plugin-catalog@1.20.1-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## example-backend@0.0.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-search-backend@1.5.10-next.1 + - @backstage/backend-defaults@0.3.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-scaffolder-backend@1.22.8-next.1 + - @backstage/plugin-notifications-backend@0.3.0-next.1 + - @backstage/plugin-app-backend@0.3.68-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.5-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.37-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-devtools-backend@0.3.5-next.1 + - @backstage/plugin-permission-backend@0.5.43-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.16-next.0 + - @backstage/plugin-proxy-backend@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-signals-backend@0.1.5-next.1 + - @backstage/plugin-techdocs-backend@1.10.6-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.1 + +## example-backend-legacy@0.2.99-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-search-backend@1.5.10-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.1 + - @backstage/plugin-kubernetes-backend@0.18.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-scaffolder-backend@1.22.8-next.1 + - @backstage/plugin-app-backend@0.3.68-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-devtools-backend@0.3.5-next.1 + - @backstage/plugin-events-backend@0.3.6-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-permission-backend@0.5.43-next.1 + - @backstage/plugin-proxy-backend@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.20-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.36-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.28-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-signals-backend@0.1.5-next.1 + - @backstage/plugin-techdocs-backend@1.10.6-next.1 + - example-app@0.2.98-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.1 + - @backstage/plugin-signals-node@0.1.5-next.1 + +## @internal/plugin-todo-list-backend@1.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 diff --git a/package.json b/package.json index 7b7692623a..46041abf6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.28.0-next.0", + "version": "1.28.0-next.1", "private": true, "repository": { "type": "git", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 1bf9501f3b..4a58195198 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,21 @@ # example-app-next +## 0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.7-next.1 + - @backstage/plugin-catalog-import@0.12.0-next.1 + - @backstage/plugin-kubernetes@0.11.11-next.1 + - @backstage/plugin-search@1.4.12-next.1 + - @backstage/plugin-notifications@0.2.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder@1.20.2-next.1 + - @backstage/plugin-catalog@1.20.1-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.0.12-next.0 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 1921ebe3cb..3b1bf6a29a 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.12-next.0", + "version": "0.0.12-next.1", "private": true, "repository": { "type": "git", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 9d1606c9f4..01e12da7db 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,21 @@ # example-app +## 0.2.98-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.7-next.1 + - @backstage/plugin-catalog-import@0.12.0-next.1 + - @backstage/plugin-kubernetes@0.11.11-next.1 + - @backstage/plugin-search@1.4.12-next.1 + - @backstage/plugin-notifications@0.2.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder@1.20.2-next.1 + - @backstage/plugin-catalog@1.20.1-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.2.98-next.0 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 013a3f7c41..c62624c770 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.98-next.0", + "version": "0.2.98-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 302c14d6f1..74d39824e0 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/backend-app-api +## 0.7.6-next.1 + +### Patch Changes + +- 398b82a: Add support for JWKS tokens in ExternalTokenHandler. +- 9e63318: Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.7.6-next.0 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 988dfd3db9..d2f21e4998 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-app-api", - "version": "0.7.6-next.0", + "version": "0.7.6-next.1", "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 5d5920998b..62a9108d8a 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/backend-common +## 0.23.0-next.1 + +### Minor Changes + +- 02103be: Deprecated and moved over core services to `@backstage/backend-defaults` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.22.1-next.0 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 564d9589bf..6a72699739 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-common", - "version": "0.22.1-next.0", + "version": "0.23.0-next.1", "description": "Common functionality library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index fe8952c64a..8019597e55 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/backend-defaults +## 0.3.0-next.1 + +### Minor Changes + +- 02103be: Deprecated and moved over core services to `@backstage/backend-defaults` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.2.19-next.0 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index f1eb7e0718..2e8c8cf757 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.2.19-next.0", + "version": "0.3.0-next.1", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index 01b4447eb0..2f8bb9f71b 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/backend-dynamic-feature-service +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-backend@0.3.6-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 34567a6c20..f9e4823f21 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-dynamic-feature-service", "description": "Backstage dynamic feature service", - "version": "0.2.11-next.0", + "version": "0.2.11-next.1", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-legacy/CHANGELOG.md b/packages/backend-legacy/CHANGELOG.md index 183b4a4954..0484874683 100644 --- a/packages/backend-legacy/CHANGELOG.md +++ b/packages/backend-legacy/CHANGELOG.md @@ -1,5 +1,42 @@ # example-backend-legacy +## 0.2.99-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-search-backend@1.5.10-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.1 + - @backstage/plugin-kubernetes-backend@0.18.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-scaffolder-backend@1.22.8-next.1 + - @backstage/plugin-app-backend@0.3.68-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-devtools-backend@0.3.5-next.1 + - @backstage/plugin-events-backend@0.3.6-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-permission-backend@0.5.43-next.1 + - @backstage/plugin-proxy-backend@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.20-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.36-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.28-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-signals-backend@0.1.5-next.1 + - @backstage/plugin-techdocs-backend@1.10.6-next.1 + - example-app@0.2.98-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.1 + - @backstage/plugin-signals-node@0.1.5-next.1 + ## 0.2.99-next.0 ### Patch Changes diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index cad1a50b7f..abdf3cb500 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-legacy", - "version": "0.2.99-next.0", + "version": "0.2.99-next.1", "backstage": { "role": "backend" }, diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 74b0cfdab9..0676d76360 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-plugin-api +## 0.6.19-next.1 + +### Patch Changes + +- 9e63318: Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions. +- 0665b7e: Renamed `BackendPluginConfig`, `BackendModuleConfig`, and `ExtensionPointConfig` respectively to `CreateBackendPluginOptions`, `CreateBackendModuleOptions`, and `CreateExtensionPointOptions` to standardize frontend and backend factories signatures. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.6.19-next.0 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index ec84d31a45..7fbd8d8b2f 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-plugin-api", - "version": "0.6.19-next.0", + "version": "0.6.19-next.1", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 25290d5575..a8dd81ce25 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-tasks +## 0.5.24-next.1 + +### Patch Changes + +- ed473cd: Updated the `TaskScheduleDefinitionConfig` deprecated comment to point to `SchedulerServiceTaskScheduleDefinitionConfig` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 0.5.24-next.0 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index c57e334185..8a4de36426 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.5.24-next.0", + "version": "0.5.24-next.1", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index aaf2cf4e27..c9a93857f7 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/backend-test-utils +## 0.4.0-next.1 + +### Minor Changes + +- 805cbe7: Added `TestCaches` that functions just like `TestDatabases` + +### Patch Changes + +- 9e63318: Made it possible to give access restrictions to `mockCredentials.service` +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.3.9-next.0 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index c84956e58a..dfc3ea82cc 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-test-utils", - "version": "0.3.9-next.0", + "version": "0.4.0-next.1", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 3b4104ece6..a8937ec456 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,39 @@ # example-backend +## 0.0.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-search-backend@1.5.10-next.1 + - @backstage/backend-defaults@0.3.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-scaffolder-backend@1.22.8-next.1 + - @backstage/plugin-notifications-backend@0.3.0-next.1 + - @backstage/plugin-app-backend@0.3.68-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.5-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.37-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-devtools-backend@0.3.5-next.1 + - @backstage/plugin-permission-backend@0.5.43-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.16-next.0 + - @backstage/plugin-proxy-backend@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-signals-backend@0.1.5-next.1 + - @backstage/plugin-techdocs-backend@1.10.6-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.1 + ## 0.0.27-next.0 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 4ab3798633..999d4dcc46 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.27-next.0", + "version": "0.0.27-next.1", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index f3b7d52c7e..3ad3bf3fe5 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/cli +## 0.26.7-next.1 + +### Patch Changes + +- 788eca7: Fix readme for new plugins created using cli +- c00f7ee: Fix issue with `esm` loaded dependencies being different from the `cjs` import for Vite dependencies +- Updated dependencies + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + ## 0.26.6-next.0 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 8479ff192e..e66202b92c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.26.6-next.0", + "version": "0.26.7-next.1", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 6fa1ef51c8..67560bf6c3 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/create-app +## 0.5.16-next.1 + +### Patch Changes + +- Bumped create-app version. + ## 0.5.16-next.0 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index d8bfb337fa..5f6566e78a 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.5.16-next.0", + "version": "0.5.16-next.1", "publishConfig": { "access": "public" }, diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 4e4707d7f5..0f44f1ca88 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/repo-tools +## 0.9.1-next.1 + +### Patch Changes + +- 8721a02: Add `--client-additional-properties` option to `openapi generate` command +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + ## 0.9.1-next.0 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index dd0ba95ee7..fa6e99485a 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.9.1-next.0", + "version": "0.9.1-next.1", "publishConfig": { "access": "public" }, diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index a154513749..6f72a73b79 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @techdocs/cli +## 1.8.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + ## 1.8.12-next.0 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 3edd15e192..8db3eace5e 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.8.12-next.0", + "version": "1.8.12-next.1", "publishConfig": { "access": "public" }, diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 77de6588f4..4be30962c0 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-app-backend +## 0.3.68-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.3.68-next.0 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 177b91a202..0b8bf97107 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.68-next.0", + "version": "0.3.68-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index 1da1830fab..ba07bcf852 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index 3d2f4f890b..924b7323e9 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", "description": "The aws-alb provider module for the Backstage auth backend.", - "version": "0.1.11-next.0", + "version": "0.1.11-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index e532415c6b..d3f83d4c6c 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.1.2-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per https://backstage.io/docs/architecture-decisions/adrs-adr013 +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index a332207890..67b1ad284b 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/package.json +++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider", - "version": "0.1.2-next.0", + "version": "0.1.2-next.1", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index ae130e0fe2..4afdc14430 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index 86276daea3..c29e5739f4 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.1.5-next.0", + "version": "0.1.5-next.1", "description": "The guest-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 191f459689..50d88f11b7 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.1.10-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index cbdffb0f02..5b7701e96c 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", "description": "The oidc-provider backend module for the auth plugin.", - "version": "0.1.10-next.0", + "version": "0.1.10-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index fd910b9377..4f12db77b8 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-auth-backend +## 0.22.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.2-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.11-next.0 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.11-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.14-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.14-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.12-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.10-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.12-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.22.6-next.0 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index cccb390b5b..c3606041f6 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.22.6-next.0", + "version": "0.22.6-next.1", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin" diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index b60cf12574..5135ff8c91 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-node +## 0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 0.4.14-next.0 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 5fad67b237..4e6057f30e 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.4.14-next.0", + "version": "0.4.14-next.1", "backstage": { "role": "node-library" }, diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 13280d414a..0ec8426f1c 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.3.14-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 69d692a4cc..a36211ec13 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.3.14-next.0", + "version": "0.3.14-next.1", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 205c5a2b17..8b21c42d1f 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.39-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 221e0058eb..aa476f0ebc 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.1.39-next.0", + "version": "0.1.39-next.1", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index c02efbc491..2acbc2edc8 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.2.6-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 667bdf4a23..37d6978721 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.2.6-next.0", + "version": "0.2.6-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 534be4549d..d719c3ed22 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.33-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index d9e46ebec4..6b8bddb330 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.33-next.0", + "version": "0.1.33-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 3903d46108..e315bb0d4c 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.20-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index b832dfafab..2233d76705 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.1.20-next.0", + "version": "0.1.20-next.1", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 206c04b47c..8db092ca63 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.36-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 130ba40687..8a636884f2 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.36-next.0", + "version": "0.1.36-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 6a9793f161..213e9eb3f8 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend-module-github@0.6.2-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.1.14-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index a6d2f7cfcc..f1dc9303d7 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.1.14-next.0", + "version": "0.1.14-next.1", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index b25241c192..3649676fef 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-github +## 0.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.6.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 4a53a7d389..1c7c110d7f 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.6.2-next.0", + "version": "0.6.2-next.1", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 58f9136ac5..8b309890eb 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.17-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.0.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 91972cd3df..e763b59b64 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.0.2-next.0", + "version": "0.0.2-next.1", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index d4fc84ed2a..11556923cc 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.3.17-next.1 + +### Patch Changes + +- 150fc77: Fixed an issue in `GitlabOrgDiscoveryEntityProvider` where a missing `orgEnabled` config key was throwing an error. +- f271164: Fixed an issue in `GitlabDiscoveryEntityProvider` where the fallback branch was taking precedence over the GitLab default branch. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.3.17-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index c4d4c07131..0d8379dc6c 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", - "version": "0.3.17-next.0", + "version": "0.3.17-next.1", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 5051cf43db..a25cb9f75a 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.4.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.4.24-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index ab4bacf252..951c0d9f93 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.4.24-next.0", + "version": "0.4.24-next.1", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 6410c0bde3..a7a1efa77b 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.6.0-next.1 + +### Minor Changes + +- debcc8c: Migrate LDAP catalog module to the new backend system. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.5.35-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 8bfa0905f6..5c954561e9 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.5.35-next.0", + "version": "0.6.0-next.1", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 63b43005c7..7bbd4cf0fc 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.5.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.5.27-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index f031e85329..a2c579aaf2 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.5.27-next.0", + "version": "0.5.27-next.1", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index b02043655b..4c3fa26383 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.37-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 7e4d72a5f1..5be17c2b69 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.1.37-next.0", + "version": "0.1.37-next.1", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index 1fb2d5dc75..431d15bb64 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.25-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 43ec390541..a3eeaa5ee9 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.1.25-next.0", + "version": "0.1.25-next.1", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index 290f7acd35..5fe8cae147 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.17-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index f1842779e5..7f6109c823 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.1.17-next.0", + "version": "0.1.17-next.1", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index 61d8ab845b..4111b25411 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.4.6-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 5d78cde6ae..e01b636905 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", - "version": "0.4.6-next.0", + "version": "0.4.6-next.1", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 22ca3b4f89..836d2282f3 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend +## 1.23.0-next.1 + +### Patch Changes + +- d779e3b: Added a regex test to check commit hash. If url is from git commit branch ignore the edit url. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + ## 1.23.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index a924881a4d..53bc27ecd6 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.23.0-next.0", + "version": "1.23.0-next.1", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin" diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index a72fb070da..407d437889 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-import +## 0.12.0-next.1 + +### Minor Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.11.1-next.0 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 1aec15f541..8d6410bf76 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.11.1-next.0", + "version": "0.12.0-next.1", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 23d7fee635..c3623b8182 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog +## 1.20.1-next.1 + +### Patch Changes + +- a2d2649: Export `catalogTranslationRef` under `/alpha` +- bcec60f: updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 1.20.1-next.0 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 488bc05b4b..eefdcdf706 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.20.1-next.0", + "version": "1.20.1-next.1", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index d0c2f7d6fc..fd3e6d21bf 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-devtools-backend +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 3b6ee4af31..bccd406ae0 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.3.5-next.0", + "version": "0.3.5-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 42ce92265e..47e43c4265 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 1a4951ae2f..3c1a796d5f 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-aws-sqs", - "version": "0.3.5-next.0", + "version": "0.3.5-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index 84337dec34..0dff94a986 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.3.6-next.0 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 5bbcd48919..ae19b98d6e 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.3.6-next.0", + "version": "0.3.6-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index 1350ca84c5..a380b2f707 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @internal/plugin-todo-list-backend +## 1.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 1.0.28-next.0 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 60aa287efb..1c70aef305 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.28-next.0", + "version": "1.0.28-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 6b742d4f4d..98e3f983f3 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-kubernetes-backend +## 0.18.0-next.1 + +### Minor Changes + +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-kubernetes-node@0.1.13-next.1 + ## 0.17.2-next.0 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 678e68816f..e3bf25bad2 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.17.2-next.0", + "version": "0.18.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index fe667ca054..1ed061ecb0 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.4.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.0.12-next.0 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 67527562a6..b1547ed24d 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.12-next.0", + "version": "0.0.12-next.1", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 778364af1b..7bf7741f3f 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-kubernetes-common +## 0.8.0-next.0 + +### Minor Changes + +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + ## 0.7.6 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index f3bba8b855..64807dd61f 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-common", - "version": "0.7.6", + "version": "0.8.0-next.0", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", "backstage": { "role": "common-library" diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index fd1481b0a1..d01ce7dc43 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kubernetes-node +## 0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + ## 0.1.13-next.0 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index fbc187750a..701106dfd0 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.1.13-next.0", + "version": "0.1.13-next.1", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library" diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index 7d94edb692..ae1879f2b1 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-react +## 0.4.0-next.1 + +### Minor Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + ## 0.3.6-next.0 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 47f6cd0c25..71e5213d62 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-react", "description": "Web library for the kubernetes-react plugin", - "version": "0.3.6-next.0", + "version": "0.4.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index fcfbfdb6d2..77c059aa4f 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes +## 0.11.11-next.1 + +### Patch Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.4.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.11.11-next.0 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index bd47686e63..75ee785bf9 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.11.11-next.0", + "version": "0.11.11-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index e2b1e0cea4..dc4b0238e5 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-notifications-backend-module-email +## 0.1.0-next.1 + +### Minor Changes + +- 07a789b: add notification filters + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 0.0.2-next.0 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index d94048978e..21b47d9de4 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.0.2-next.0", + "version": "0.1.0-next.1", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 923696afce..28929c9334 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-notifications-backend +## 0.3.0-next.1 + +### Minor Changes + +- 07a789b: adding filtering of notifications by processors + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-signals-node@0.1.5-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 3d809ba11b..03e7214d85 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.2.2-next.0", + "version": "0.3.0-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index eca0358484..846b305f5e 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-notifications-node +## 0.2.0-next.1 + +### Minor Changes + +- 07a789b: add notifications filtering by processors + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per https://backstage.io/docs/architecture-decisions/adrs-adr013 +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-signals-node@0.1.5-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 2500026f3b..b566da41f5 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.1.5-next.0", + "version": "0.2.0-next.1", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library" diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 08affa74d9..66a02ee34a 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-notifications +## 0.2.2-next.1 + +### Patch Changes + +- 6d196b4: Fixes performance issue with Notifications title counter. + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 6c975c8557..0e54a64f86 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.2.2-next.0", + "version": "0.2.2-next.1", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 46c70631c1..58db983a93 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-permission-backend +## 0.5.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.5.43-next.0 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index c09007b817..33c95f7997 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.43-next.0", + "version": "0.5.43-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 756b93a37c..6a394c781d 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-permission-node +## 0.7.30-next.1 + +### Patch Changes + +- 9e63318: Ensure that service token access restrictions, when present, are taken into account +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.7.30-next.0 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 254ef17d00..1878e4db08 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.7.30-next.0", + "version": "0.7.30-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 2db699508f..dac4e39607 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-proxy-backend +## 0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 0.5.0-next.0 ### Minor Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index af0d9d584e..59d9fb522d 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.5.0-next.0", + "version": "0.5.0-next.1", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index e152ffeba6..ba99c297e7 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.2.20-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 67b85ef18e..7839fa0dc0 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.2.20-next.0", + "version": "0.2.20-next.1", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index c3e2893dc6..291f711152 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.2.43-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 2d750a1086..6a1ab577aa 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.2.43-next.0", + "version": "0.2.43-next.1", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 373b06a638..099862e4b5 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.1.9-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per https://backstage.io/docs/architecture-decisions/adrs-adr013 +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.1.9-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 0d6baeaaa0..1f89cdc366 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.9-next.0", + "version": "0.1.9-next.1", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index a375e2202b..53091b92cb 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.2.9-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 6338947116..88eeb2860e 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.2.9-next.0", + "version": "0.2.9-next.1", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index f7d18d703a..3464ff436a 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.4.1-next.1 + +### Patch Changes + +- 829e0ec: Add new `gitlab:pipeline:trigger` action to trigger GitLab pipelines. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.4.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index b91b36bd55..bb7be8363b 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.4.1-next.0", + "version": "0.4.1-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index 8e5994dd23..6c004bee97 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.0.2-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index fcebc0c511..c98eea2b8f 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.0.2-next.0", + "version": "0.0.2-next.1", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 1c174579bb..4afe60a1fe 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.4.36-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 62ba9ff77c..08b45f598c 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.4.36-next.0", + "version": "0.4.36-next.1", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index 6f5d470dab..ca4532e8ce 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.1.27-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per https://backstage.io/docs/architecture-decisions/adrs-adr013 +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.1.27-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 861e78b565..c5930717c6 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.1.27-next.0", + "version": "0.1.27-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 477470b8c9..9e087788a6 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-scaffolder-backend +## 1.22.8-next.1 + +### Patch Changes + +- bcec60f: added the following new permissions to the scaffolder backend endpoints: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.9-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.11-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 1.22.8-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 0caa20c410..b1901e4fcd 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.22.8-next.0", + "version": "1.22.8-next.1", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 8b5c2d7c99..cbd9579ce9 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-common +## 1.5.3-next.0 + +### Patch Changes + +- bcec60f: added the following new permissions to the scaffolder backend endpoints: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + ## 1.5.2 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index a0060b1427..04f3375520 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-common", - "version": "1.5.2", + "version": "1.5.3-next.0", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library" diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index 770f78ef77..574d08d934 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.4.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index cd0cac649d..48f3ea22a7 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.1.5-next.0", + "version": "0.1.5-next.1", "backstage": { "role": "node-library" }, diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index 0dc1ffeec8..579ded0816 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-node +## 0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + ## 0.4.5-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 5f2834320f..900bf25b06 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.4.5-next.0", + "version": "0.4.5-next.1", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library" diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 84c442c040..c5327627bd 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,1418 @@ # @backstage/plugin-scaffolder-react +## 1.8.7-next.1 + +### Patch Changes + +- 75dcd7e: Fixing bug in `formData` type as it should be `optional` as it's possibly undefined +- 928cfa0: Fixed a typo ' + +## 1.8.6-next.0 + +### Patch Changes + +- 86dc29d: Links that are rendered in the markdown in the `ScaffolderField` component are now opened in new tabs. +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-scaffolder-common@1.5.2 + +## 1.8.5 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/catalog-client@1.6.5 + +## 1.8.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + +## 1.8.5-next.1 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + +## 1.8.5-next.0 + +### Patch Changes + +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.8.4 + +### Patch Changes + +- abfbcfc: Updated dependency `@testing-library/react` to `^15.0.0`. +- 87d2eb8: Updated dependency `json-schema-library` to `^9.0.0`. +- cb1e3b0: Updated dependency `@testing-library/dom` to `^10.0.0`. +- 0e692cf: Added ESLint rule `no-top-level-material-ui-4-imports` to migrate the Material UI imports. +- df99f62: The `value` sent on the `create` analytics event (fired when a Scaffolder template is executed) is now set to the number of minutes saved by executing the template. This value is derived from the `backstage.io/time-saved` annotation on the template entity, if available. + + Note: the `create` event is now captured in the `` component. If you are directly making use of the alpha-exported `` component, an analytics `create` event will no longer be captured on your behalf. + +- Updated dependencies + - @backstage/plugin-catalog-react@1.11.3 + - @backstage/core-components@0.14.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/theme@0.5.3 + - @backstage/version-bridge@1.0.8 + - @backstage/catalog-client@1.6.4 + - @backstage/catalog-model@1.4.5 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.4-next.1 + +### Patch Changes + +- 87d2eb8: Updated dependency `json-schema-library` to `^9.0.0`. +- df99f62: The `value` sent on the `create` analytics event (fired when a Scaffolder template is executed) is now set to the number of minutes saved by executing the template. This value is derived from the `backstage.io/time-saved` annotation on the template entity, if available. + + Note: the `create` event is now captured in the `` component. If you are directly making use of the alpha-exported `` component, an analytics `create` event will no longer be captured on your behalf. + +- Updated dependencies + - @backstage/catalog-client@1.6.4-next.0 + - @backstage/catalog-model@1.4.5 + - @backstage/core-components@0.14.4-next.0 + - @backstage/core-plugin-api@1.9.1 + - @backstage/theme@0.5.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-react@1.11.3-next.1 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.4-next.0 + - @backstage/catalog-client@1.6.3 + - @backstage/catalog-model@1.4.5 + - @backstage/core-plugin-api@1.9.1 + - @backstage/theme@0.5.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-react@1.11.3-next.0 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.3 + +### Patch Changes + +- e8f026a: Use ESM exports of react-use library +- Updated dependencies + - @backstage/catalog-client@1.6.3 + - @backstage/core-components@0.14.3 + - @backstage/plugin-catalog-react@1.11.2 + - @backstage/core-plugin-api@1.9.1 + - @backstage/catalog-model@1.4.5 + - @backstage/theme@0.5.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.2 + +### Patch Changes + +- e8f026a: Use ESM exports of react-use library +- Updated dependencies + - @backstage/catalog-client@1.6.2 + - @backstage/core-components@0.14.2 + - @backstage/plugin-catalog-react@1.11.1 + - @backstage/core-plugin-api@1.9.1 + - @backstage/catalog-model@1.4.5 + - @backstage/theme@0.5.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.1 + +### Patch Changes + +- 930b5c1: Added 'root' and 'label' class key to TemplateCategoryPicker +- 6d649d2: Updated dependency `flatted` to `3.3.1`. +- 0cecb09: Updated dependency `@rjsf/utils` to `5.17.1`. + Updated dependency `@rjsf/core` to `5.17.1`. + Updated dependency `@rjsf/material-ui` to `5.17.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.17.1`. +- Updated dependencies + - @backstage/core-components@0.14.1 + - @backstage/theme@0.5.2 + - @backstage/plugin-catalog-react@1.11.0 + - @backstage/catalog-client@1.6.1 + - @backstage/catalog-model@1.4.5 + - @backstage/core-plugin-api@1.9.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.1-next.2 + - @backstage/plugin-catalog-react@1.11.0-next.2 + - @backstage/catalog-client@1.6.1-next.1 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/core-plugin-api@1.9.1-next.1 + - @backstage/theme@0.5.2-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1-next.1 + +## 1.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.1-next.1 + - @backstage/plugin-catalog-react@1.10.1-next.1 + - @backstage/core-plugin-api@1.9.1-next.1 + - @backstage/catalog-client@1.6.1-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/theme@0.5.2-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1-next.1 + +## 1.8.1-next.0 + +### Patch Changes + +- 930b5c1: Added 'root' and 'label' class key to TemplateCategoryPicker +- 6d649d2: Updated dependency `flatted` to `3.3.1`. +- 0cecb09: Updated dependency `@rjsf/utils` to `5.17.1`. + Updated dependency `@rjsf/core` to `5.17.1`. + Updated dependency `@rjsf/material-ui` to `5.17.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.17.1`. +- Updated dependencies + - @backstage/theme@0.5.2-next.0 + - @backstage/core-components@0.14.1-next.0 + - @backstage/plugin-catalog-react@1.10.1-next.0 + - @backstage/catalog-client@1.6.1-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/core-plugin-api@1.9.1-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1-next.0 + +## 1.8.0 + +### Minor Changes + +- c56f1a2: Remove the old legacy exports from `/alpha` +- 11b9a08: Introduced the first version of recoverable tasks. +- b07ec70: Use more distinguishable icons for link (`Link`) and text output (`Description`). + +### Patch Changes + +- 3f60ad5: fix for: converting circular structure to JSON error +- 0b0c6b6: Allow defining default output text to be shown +- 8fe56a8: Widen `@types/react` dependency range to include version 18. +- 31f0a0a: Added `ScaffolderPageContextMenu` to `ActionsPage`, `ListTaskPage`, and `TemplateEditorPage` so that you can more easily navigate between these pages +- 09cedb9: Updated dependency `@react-hookz/web` to `^24.0.0`. +- e6f0831: Updated dependency `@rjsf/utils` to `5.17.0`. + Updated dependency `@rjsf/core` to `5.17.0`. + Updated dependency `@rjsf/material-ui` to `5.17.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.17.0`. +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- 3dff4b0: Remove unused deps +- 82affc7: Fix issue where `ui:schema` was replaced with an empty object if `dependencies` is defined +- 2985186: Fix bug that erroneously caused a separator or a 0 to render in the TemplateCard for Templates with empty links +- Updated dependencies + - @backstage/plugin-catalog-react@1.10.0 + - @backstage/core-components@0.14.0 + - @backstage/catalog-model@1.4.4 + - @backstage/theme@0.5.1 + - @backstage/core-plugin-api@1.9.0 + - @backstage/catalog-client@1.6.0 + - @backstage/plugin-scaffolder-common@1.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.0-next.3 + +### Patch Changes + +- 09cedb9: Updated dependency `@react-hookz/web` to `^24.0.0`. +- e6f0831: Updated dependency `@rjsf/utils` to `5.17.0`. + Updated dependency `@rjsf/core` to `5.17.0`. + Updated dependency `@rjsf/material-ui` to `5.17.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.17.0`. +- Updated dependencies + - @backstage/theme@0.5.1-next.1 + - @backstage/core-components@0.14.0-next.2 + - @backstage/plugin-catalog-react@1.10.0-next.3 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/core-plugin-api@1.9.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.0-next.1 + +## 1.8.0-next.2 + +### Patch Changes + +- 8fe56a8: Widen `@types/react` dependency range to include version 18. +- 2985186: Fix bug that erroneously caused a separator or a 0 to render in the TemplateCard for Templates with empty links +- Updated dependencies + - @backstage/core-components@0.14.0-next.1 + - @backstage/core-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-react@1.10.0-next.2 + - @backstage/theme@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.0-next.1 + +## 1.8.0-next.1 + +### Minor Changes + +- b07ec70: Use more distinguishable icons for link (`Link`) and text output (`Description`). + +### Patch Changes + +- 3f60ad5: fix for: converting circular structure to JSON error +- 31f0a0a: Added `ScaffolderPageContextMenu` to `ActionsPage`, `ListTaskPage`, and `TemplateEditorPage` so that you can more easily navigate between these pages +- 82affc7: Fix issue where `ui:schema` was replaced with an empty object if `dependencies` is defined +- Updated dependencies + - @backstage/core-components@0.14.0-next.0 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/core-plugin-api@1.8.3-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.0-next.1 + +## 1.8.0-next.0 + +### Minor Changes + +- c56f1a2: Remove the old legacy exports from `/alpha` +- 11b9a08: Introduced the first version of recoverable tasks. + +### Patch Changes + +- 0b0c6b6: Allow defining default output text to be shown +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.7.1 + +### Patch Changes + +- c28f281: Scaffolder form now shows a list of errors at the top of the form. +- 0b9ce2b: Fix for a step with no properties +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- 4016f21: Remove some unused dependencies +- d16f85f: Show first scaffolder output text by default +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.7.1-next.2 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 1.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.7.1-next.0 + +### Patch Changes + +- c28f281: Scaffolder form now shows a list of errors at the top of the form. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.7.0 + +### Minor Changes + +- 33edf50: Added support for dealing with user provided secrets using a new field extension `ui:field: Secret` + +### Patch Changes + +- 670c7cc: Fix bug where `properties` is set to empty object when it should be empty for schema dependencies +- fa66d1b: Fixed bug in `ReviewState` where `enum` value was displayed in step review instead of the corresponding label when using `enumNames` +- e516bf4: Step titles in the Stepper are now clickable and redirect the user to the corresponding step, as an alternative to using the back buttons. +- aaa6fb3: Minor updates for TypeScript 5.2.2+ compatibility +- 2aee53b: Add horizontal slider if stepper overflows +- 2b72591: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- 6cd12f2: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 63c494e: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- c8908d4: Use new option from RJSF 5.15 +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- 5bb5240: Fixed issue for showing undefined for hidden form items +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.6.2-next.3 + +### Patch Changes + +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- c8908d4: Use new option from RJSF 5.15 +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.6.2-next.2 + +### Patch Changes + +- 5bb5240: Fixed issue for showing undefined for hidden form items +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.6.2-next.1 + +### Patch Changes + +- fa66d1b5b3: Fixed bug in `ReviewState` where `enum` value was displayed in step review instead of the corresponding label when using `enumNames` +- 2aee53bbeb: Add horizontal slider if stepper overflows +- 2b725913c1: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.6.2-next.0 + +### Patch Changes + +- e516bf4da8: Step titles in the Stepper are now clickable and redirect the user to the corresponding step, as an alternative to using the back buttons. +- aaa6fb3bc9: Minor updates for TypeScript 5.2.2+ compatibility +- 6cd12f277b: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- 63c494ef22: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.0 + - @backstage/plugin-catalog-react@1.9.2-next.0 + - @backstage/core-components@0.13.9-next.0 + - @backstage/theme@0.5.0-next.0 + - @backstage/catalog-client@1.4.6 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.6.0 + +### Minor Changes + +- 3fdffbb699: Release design improvements for the `Scaffolder` plugin and support v5 of `@rjsf/*` libraries. + + This change should be non-breaking. If you're seeing typescript issues after migrating please [open an issue](https://github.com/backstage/backstage/issues/new/choose) + + The `next` versions like `createNextFieldExtension` and `NextScaffolderPage` have been promoted to the public interface under `createScaffolderFieldExtension` and `ScaffolderPage`, so any older imports which are no longer found will need updating from `@backstage/plugin-scaffolder/alpha` or `@backstage/plugin-scaffolder-react/alpha` will need to be imported from `@backstage/plugin-scaffolder` and `@backstage/plugin-scaffolder-react` respectively. + + The legacy versions are now available in `/alpha` under `createLegacyFieldExtension` and `LegacyScaffolderPage` if you're running into issues, but be aware that these will be removed in a next mainline release. + +### Patch Changes + +- 6c2b872153: Add official support for React 18. +- 171a99816b: Fixed `backstage:featureFlag` in `scaffolder/next` by sorting out `manifest.steps`. +- c838da0edd: Updated dependency `@rjsf/utils` to `5.13.6`. + Updated dependency `@rjsf/core` to `5.13.6`. + Updated dependency `@rjsf/material-ui` to `5.13.6`. + Updated dependency `@rjsf/validator-ajv8` to `5.13.6`. +- 69c14904b6: Use `EntityRefLinks` with `hideIcons` property to avoid double icons +- 62b5922916: Internal theme type updates +- dda56ae265: Preserve step's time execution for a non-running task. +- 76d07da66a: Make it possible to define control buttons text (Back, Create, Review) per template +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.0 + - @backstage/core-components@0.13.8 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/core-plugin-api@1.8.0 + - @backstage/version-bridge@1.0.7 + - @backstage/theme@0.4.4 + - @backstage/catalog-client@1.4.6 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.8-next.2 + - @backstage/plugin-catalog-react@1.9.0-next.2 + +## 1.6.0-next.1 + +### Patch Changes + +- 62b5922916: Internal theme type updates +- 76d07da66a: Make it possible to define control buttons text (Back, Create, Review) per template +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.0-next.1 + - @backstage/plugin-scaffolder-common@1.4.3-next.1 + - @backstage/core-components@0.13.8-next.1 + - @backstage/catalog-client@1.4.5 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/theme@0.4.4-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7-next.0 + +## 1.6.0-next.0 + +### Minor Changes + +- 3fdffbb699: Release design improvements for the `Scaffolder` plugin and support v5 of `@rjsf/*` libraries. + + This change should be non-breaking. If you're seeing typescript issues after migrating please [open an issue](https://github.com/backstage/backstage/issues/new/choose) + + The `next` versions like `createNextFieldExtension` and `NextScaffolderPage` have been promoted to the public interface under `createScaffolderFieldExtension` and `ScaffolderPage`, so any older imports which are no longer found will need updating from `@backstage/plugin-scaffolder/alpha` or `@backstage/plugin-scaffolder-react/alpha` will need to be imported from `@backstage/plugin-scaffolder` and `@backstage/plugin-scaffolder-react` respectively. + + The legacy versions are now available in `/alpha` under `createLegacyFieldExtension` and `LegacyScaffolderPage` if you're running into issues, but be aware that these will be removed in a next mainline release. + +### Patch Changes + +- 6c2b872153: Add official support for React 18. +- Updated dependencies + - @backstage/core-components@0.13.7-next.0 + - @backstage/plugin-scaffolder-common@1.4.3-next.0 + - @backstage/plugin-catalog-react@1.9.0-next.0 + - @backstage/core-plugin-api@1.8.0-next.0 + - @backstage/version-bridge@1.0.7-next.0 + - @backstage/theme@0.4.4-next.0 + - @backstage/catalog-client@1.4.5 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.5.6 + +### Patch Changes + +- 9a1fce352e: Updated dependency `@testing-library/jest-dom` to `^6.0.0`. +- f95af4e540: Updated dependency `@testing-library/dom` to `^9.0.0`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.5 + - @backstage/core-plugin-api@1.7.0 + - @backstage/core-components@0.13.6 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/version-bridge@1.0.6 + - @backstage/theme@0.4.3 + - @backstage/catalog-client@1.4.5 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.2 + +## 1.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.6-next.2 + - @backstage/core-plugin-api@1.7.0-next.1 + - @backstage/catalog-model@1.4.3-next.0 + - @backstage/plugin-catalog-react@1.8.5-next.2 + - @backstage/errors@1.2.3-next.0 + - @backstage/theme@0.4.3-next.0 + - @backstage/catalog-client@1.4.5-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + - @backstage/plugin-scaffolder-common@1.4.2-next.0 + +## 1.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.6-next.1 + - @backstage/plugin-catalog-react@1.8.5-next.1 + - @backstage/core-plugin-api@1.7.0-next.0 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/errors@1.2.2 + - @backstage/theme@0.4.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + - @backstage/plugin-scaffolder-common@1.4.1 + +## 1.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.5-next.0 + - @backstage/core-plugin-api@1.7.0-next.0 + - @backstage/core-components@0.13.6-next.0 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/errors@1.2.2 + - @backstage/theme@0.4.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + - @backstage/plugin-scaffolder-common@1.4.1 + +## 1.5.5 + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- b16c341ced45: Updated dependency `@rjsf/utils` to `5.13.0`. + Updated dependency `@rjsf/core-v5` to `npm:@rjsf/core@5.13.0`. + Updated dependency `@rjsf/material-ui-v5` to `npm:@rjsf/material-ui@5.13.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.13.0`. +- 27fef07f9229: Updated dependency `use-immer` to `^0.9.0`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.4 + - @backstage/core-components@0.13.5 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/core-plugin-api@1.6.0 + - @backstage/errors@1.2.2 + - @backstage/plugin-scaffolder-common@1.4.1 + - @backstage/theme@0.4.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + +## 1.5.5-next.3 + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- b16c341ced45: Updated dependency `@rjsf/utils` to `5.13.0`. + Updated dependency `@rjsf/core-v5` to `npm:@rjsf/core@5.13.0`. + Updated dependency `@rjsf/material-ui-v5` to `npm:@rjsf/material-ui@5.13.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.13.0`. +- Updated dependencies + - @backstage/catalog-client@1.4.4-next.2 + - @backstage/catalog-model@1.4.2-next.2 + - @backstage/core-components@0.13.5-next.3 + - @backstage/core-plugin-api@1.6.0-next.3 + - @backstage/errors@1.2.2-next.0 + - @backstage/plugin-catalog-react@1.8.4-next.3 + - @backstage/plugin-scaffolder-common@1.4.1-next.2 + - @backstage/theme@0.4.2-next.0 + - @backstage/types@1.1.1-next.0 + - @backstage/version-bridge@1.0.5-next.0 + +## 1.5.5-next.2 + +### Patch Changes + +- 27fef07f9229: Updated dependency `use-immer` to `^0.9.0`. +- Updated dependencies + - @backstage/core-components@0.13.5-next.2 + - @backstage/core-plugin-api@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.8.4-next.2 + - @backstage/catalog-model@1.4.2-next.1 + - @backstage/catalog-client@1.4.4-next.1 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.4.1-next.1 + +## 1.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.4-next.1 + - @backstage/core-components@0.13.5-next.1 + - @backstage/catalog-model@1.4.2-next.0 + - @backstage/core-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.4.4-next.0 + - @backstage/plugin-scaffolder-common@1.4.1-next.0 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.6.0-next.0 + - @backstage/core-components@0.13.5-next.0 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-catalog-react@1.8.3-next.0 + - @backstage/plugin-scaffolder-common@1.4.0 + +## 1.5.2 + +### Patch Changes + +- ba9ee98a37bd: Fixed bug in Workflow component by passing a prop `templateName` down to Stepper component. +- Updated dependencies + - @backstage/core-components@0.13.4 + - @backstage/plugin-catalog-react@1.8.1 + - @backstage/plugin-scaffolder-common@1.4.0 + - @backstage/core-plugin-api@1.5.3 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.1-next.1 + +## 1.5.2-next.0 + +### Patch Changes + +- ba9ee98a37bd: Fixed bug in Workflow component by passing a prop `templateName` down to Stepper component. +- Updated dependencies + - @backstage/core-components@0.13.4-next.0 + - @backstage/core-plugin-api@1.5.3 + - @backstage/plugin-catalog-react@1.8.1-next.0 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.3.2 + +## 1.5.1 + +### Patch Changes + +- f74a27de4d2c: Made markdown description theme-able +- Updated dependencies + - @backstage/theme@0.4.1 + - @backstage/errors@1.2.1 + - @backstage/plugin-catalog-react@1.8.0 + - @backstage/core-components@0.13.3 + - @backstage/core-plugin-api@1.5.3 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.3.2 + +## 1.5.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.0-next.2 + - @backstage/theme@0.4.1-next.1 + - @backstage/core-plugin-api@1.5.3-next.1 + - @backstage/core-components@0.13.3-next.2 + - @backstage/catalog-client@1.4.3-next.0 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/errors@1.2.1-next.0 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.3.2-next.0 + +## 1.5.1-next.1 + +### Patch Changes + +- f74a27de4d2c: Made markdown description theme-able +- Updated dependencies + - @backstage/theme@0.4.1-next.0 + - @backstage/core-components@0.13.3-next.1 + - @backstage/core-plugin-api@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.7.1-next.1 + +## 1.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1-next.0 + - @backstage/core-components@0.13.3-next.0 + - @backstage/catalog-client@1.4.3-next.0 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/core-plugin-api@1.5.2 + - @backstage/theme@0.4.0 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-catalog-react@1.7.1-next.0 + - @backstage/plugin-scaffolder-common@1.3.2-next.0 + +## 1.5.0 + +### Minor Changes + +- 6b571405f806: `scaffolder/next`: Provide some default template components to `rjsf` to allow for standardization and markdown descriptions +- 4505dc3b4598: `scaffolder/next`: Don't render `TemplateGroups` when there's no results in with search query +- a452bda74d7a: Fixed typescript casting bug for useTemplateParameterSchema hook +- 6b571405f806: `scaffolder/next`: provide a `ScaffolderField` component which is meant to replace some of the `FormControl` components from Material UI, making it easier to write `FieldExtensions`. + +### Patch Changes + +- 84a5c7724c7e: fixed refresh problem when backstage backend disconnects without any feedback to user. Now we send a generic message and try to reconnect after 15 seconds +- cf34311cdbe1: Extract `ui:*` fields from conditional `then` and `else` schema branches. +- 2ff94da135a4: bump `rjsf` dependencies to 5.7.3 +- 74b216ee4e50: Add `PropsWithChildren` to usages of `ComponentType`, in preparation for React 18 where the children are no longer implicit. +- Updated dependencies + - @backstage/core-plugin-api@1.5.2 + - @backstage/catalog-client@1.4.2 + - @backstage/core-components@0.13.2 + - @backstage/types@1.1.0 + - @backstage/theme@0.4.0 + - @backstage/plugin-catalog-react@1.7.0 + - @backstage/catalog-model@1.4.0 + - @backstage/errors@1.2.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.3.1 + +## 1.5.0-next.3 + +### Minor Changes + +- a452bda74d7a: Fixed typescript casting bug for useTemplateParameterSchema hook + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.2-next.3 + - @backstage/catalog-model@1.4.0-next.1 + - @backstage/catalog-client@1.4.2-next.2 + - @backstage/core-plugin-api@1.5.2-next.0 + - @backstage/errors@1.2.0-next.0 + - @backstage/theme@0.4.0-next.1 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-catalog-react@1.7.0-next.3 + - @backstage/plugin-scaffolder-common@1.3.1-next.1 + +## 1.5.0-next.2 + +### Patch Changes + +- cf34311cdbe1: Extract `ui:*` fields from conditional `then` and `else` schema branches. +- 2ff94da135a4: bump `rjsf` dependencies to 5.7.3 +- Updated dependencies + - @backstage/theme@0.4.0-next.1 + - @backstage/plugin-catalog-react@1.7.0-next.2 + - @backstage/core-components@0.13.2-next.2 + - @backstage/core-plugin-api@1.5.2-next.0 + +## 1.5.0-next.1 + +### Minor Changes + +- 6b571405f806: `scaffolder/next`: Provide some default template components to `rjsf` to allow for standardization and markdown descriptions +- 4505dc3b4598: `scaffolder/next`: Don't render `TemplateGroups` when there's no results in with search query +- 6b571405f806: `scaffolder/next`: provide a `ScaffolderField` component which is meant to replace some of the `FormControl` components from Material UI, making it easier to write `FieldExtensions`. + +### Patch Changes + +- 74b216ee4e50: Add `PropsWithChildren` to usages of `ComponentType`, in preparation for React 18 where the children are no longer implicit. +- Updated dependencies + - @backstage/errors@1.2.0-next.0 + - @backstage/core-components@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.7.0-next.1 + - @backstage/catalog-model@1.4.0-next.0 + - @backstage/core-plugin-api@1.5.2-next.0 + - @backstage/catalog-client@1.4.2-next.1 + - @backstage/plugin-scaffolder-common@1.3.1-next.0 + - @backstage/theme@0.4.0-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + +## 1.4.1-next.0 + +### Patch Changes + +- 84a5c7724c7e: fixed refresh problem when backstage backend disconnects without any feedback to user. Now we send a generic message and try to reconnect after 15 seconds +- Updated dependencies + - @backstage/catalog-client@1.4.2-next.0 + - @backstage/plugin-catalog-react@1.7.0-next.0 + - @backstage/theme@0.4.0-next.0 + - @backstage/core-components@0.13.2-next.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/catalog-model@1.3.0 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.3.0 + +## 1.4.0 + +### Minor Changes + +- 82e10a6939c: Add support for Markdown text blob outputs from templates + +### Patch Changes + +- ad1a1429de4: Improvements to the `scaffolder/next` buttons UX: + + - Added padding around the "Create" button in the `Stepper` component + - Added a button bar that includes the "Cancel" and "Start Over" buttons to the `OngoingTask` component. The state of these buttons match their existing counter parts in the Context Menu + - Added a "Show Button Bar"/"Hide Button Bar" item to the `ContextMenu` component + +- Updated dependencies + - @backstage/theme@0.3.0 + - @backstage/plugin-catalog-react@1.6.0 + - @backstage/plugin-scaffolder-common@1.3.0 + - @backstage/core-components@0.13.1 + - @backstage/catalog-client@1.4.1 + - @backstage/catalog-model@1.3.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + +## 1.4.0-next.2 + +### Minor Changes + +- 82e10a6939c: Add support for Markdown text blob outputs from templates + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.3.0-next.0 + - @backstage/plugin-scaffolder-common@1.3.0-next.0 + - @backstage/core-components@0.13.1-next.1 + - @backstage/plugin-catalog-react@1.6.0-next.2 + - @backstage/core-plugin-api@1.5.1 + +## 1.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.1-next.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/plugin-catalog-react@1.6.0-next.1 + +## 1.3.1-next.0 + +### Patch Changes + +- ad1a1429de4: Improvements to the `scaffolder/next` buttons UX: + + - Added padding around the "Create" button in the `Stepper` component + - Added a button bar that includes the "Cancel" and "Start Over" buttons to the `OngoingTask` component. The state of these buttons match their existing counter parts in the Context Menu + - Added a "Show Button Bar"/"Hide Button Bar" item to the `ContextMenu` component + +- Updated dependencies + - @backstage/plugin-catalog-react@1.6.0-next.0 + - @backstage/core-components@0.13.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/catalog-client@1.4.1 + - @backstage/catalog-model@1.3.0 + - @backstage/errors@1.1.5 + - @backstage/theme@0.2.19 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.2.7 + +## 1.3.0 + +### Minor Changes + +- 259d3407b9b: Move `CategoryPicker` from `scaffolder` into `scaffolder-react` + Move `ContextMenu` into `scaffolder-react` and rename it to `ScaffolderPageContextMenu` +- 2cfd03d7376: To offer better customization options, `ScaffolderPageContextMenu` takes callbacks as props instead of booleans +- 48da4c46e45: `scaffolder/next`: Export the `TemplateGroupFilter` and `TemplateGroups` and make an extensible component + +### Patch Changes + +- 7e1d900413a: `scaffolder/next`: Bump `@rjsf/*` dependencies to 5.5.2 +- e27ddc36dad: Added a possibility to cancel the running task (executing of a scaffolder template) +- 0435174b06f: Accessibility issues identified using lighthouse fixed. +- 7a6b16cc506: `scaffolder/next`: Bump `@rjsf/*` deps to 5.3.1 +- 90dda42cfd2: bug: Invert `templateFilter` predicate to align with `Array.filter` +- d2488f5e54c: Add an indication that the validators are running when clicking `next` on each step of the form. +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- 8c40997df44: Updated dependency `@rjsf/core-v5` to `npm:@rjsf/core@5.5.2`. +- f84fc7fd040: Updated dependency `@rjsf/validator-ajv8` to `5.3.0`. +- 8e00acb28db: Small tweaks to remove warnings in the console during development (mainly focusing on techdocs) +- 34dab7ee7f8: `scaffolder/next`: bump `rjsf` dependencies to `5.5.0` +- 2898b6c8d52: Minor type tweaks for TypeScript 5.0 +- e0c6e8b9c3c: Update peer dependencies +- cf71c3744a5: scaffolder/next: Bump `@rjsf/*` dependencies to 5.6.0 +- Updated dependencies + - @backstage/core-components@0.13.0 + - @backstage/plugin-scaffolder-common@1.2.7 + - @backstage/catalog-client@1.4.1 + - @backstage/plugin-catalog-react@1.5.0 + - @backstage/theme@0.2.19 + - @backstage/core-plugin-api@1.5.1 + - @backstage/catalog-model@1.3.0 + - @backstage/version-bridge@1.0.4 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 1.3.0-next.3 + +### Patch Changes + +- d2488f5e54c: Add indication that the validators are running +- 8c40997df44: Updated dependency `@rjsf/core-v5` to `npm:@rjsf/core@5.5.2`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.5.0-next.3 + - @backstage/catalog-model@1.3.0-next.0 + - @backstage/core-components@0.13.0-next.3 + - @backstage/catalog-client@1.4.1-next.1 + - @backstage/core-plugin-api@1.5.1-next.1 + - @backstage/errors@1.1.5 + - @backstage/theme@0.2.19-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4-next.0 + - @backstage/plugin-scaffolder-common@1.2.7-next.2 + +## 1.3.0-next.2 + +### Patch Changes + +- 90dda42cfd2: bug: Invert `templateFilter` predicate to align with `Array.filter` +- 34dab7ee7f8: `scaffolder/next`: bump `rjsf` dependencies to `5.5.0` +- 2898b6c8d52: Minor type tweaks for TypeScript 5.0 +- Updated dependencies + - @backstage/catalog-client@1.4.1-next.0 + - @backstage/core-components@0.12.6-next.2 + - @backstage/plugin-catalog-react@1.4.1-next.2 + - @backstage/core-plugin-api@1.5.1-next.1 + - @backstage/catalog-model@1.2.1 + - @backstage/errors@1.1.5 + - @backstage/theme@0.2.19-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4-next.0 + - @backstage/plugin-scaffolder-common@1.2.7-next.1 + +## 1.3.0-next.1 + +### Patch Changes + +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- e0c6e8b9c3c: Update peer dependencies +- Updated dependencies + - @backstage/core-components@0.12.6-next.1 + - @backstage/plugin-scaffolder-common@1.2.7-next.1 + - @backstage/core-plugin-api@1.5.1-next.0 + - @backstage/version-bridge@1.0.4-next.0 + - @backstage/plugin-catalog-react@1.4.1-next.1 + - @backstage/theme@0.2.19-next.0 + - @backstage/catalog-client@1.4.0 + - @backstage/catalog-model@1.2.1 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 1.3.0-next.0 + +### Minor Changes + +- 259d3407b9b: Move `CategoryPicker` from `scaffolder` into `scaffolder-react` + Move `ContextMenu` into `scaffolder-react` and rename it to `ScaffolderPageContextMenu` +- 2cfd03d7376: To offer better customization options, `ScaffolderPageContextMenu` takes callbacks as props instead of booleans +- 48da4c46e45: `scaffolder/next`: Export the `TemplateGroupFilter` and `TemplateGroups` and make an extensible component + +### Patch Changes + +- e27ddc36dad: Added a possibility to cancel the running task (executing of a scaffolder template) +- 7a6b16cc506: `scaffolder/next`: Bump `@rjsf/*` deps to 5.3.1 +- f84fc7fd040: Updated dependency `@rjsf/validator-ajv8` to `5.3.0`. +- 8e00acb28db: Small tweaks to remove warnings in the console during development (mainly focusing on techdocs) +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.2.7-next.0 + - @backstage/core-components@0.12.6-next.0 + - @backstage/plugin-catalog-react@1.4.1-next.0 + - @backstage/core-plugin-api@1.5.0 + - @backstage/catalog-client@1.4.0 + - @backstage/catalog-model@1.2.1 + - @backstage/errors@1.1.5 + - @backstage/theme@0.2.18 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.2.0 + +### Minor Changes + +- 8f4d13f21cf: Move `useTaskStream`, `TaskBorder`, `TaskLogStream` and `TaskSteps` into `scaffolder-react`. + +### Patch Changes + +- 65454876fb2: Minor API report tweaks +- 3c96e77b513: Make scaffolder adhere to page themes by using page `fontColor` consistently. If your theme overwrites template list or card headers, review those styles. +- c8d78b9ae9d: fix bug with `hasErrors` returning false when dealing with empty objects +- 9b8c374ace5: Remove timer for skipped steps in Scaffolder Next's TaskSteps +- 44941fc97eb: scaffolder/next: Move the `uiSchema` to its own property in the validation `context` to align with component development and access of `ui:options` +- d9893263ba9: scaffolder/next: Fix for steps without properties +- 928a12a9b3e: Internal refactor of `/alpha` exports. +- cc418d652a7: scaffolder/next: Added the ability to get the fields definition in the schema in the validation function +- d4100d0ec42: Fix alignment bug for owners on `TemplateCard` +- Updated dependencies + - @backstage/catalog-client@1.4.0 + - @backstage/core-components@0.12.5 + - @backstage/plugin-catalog-react@1.4.0 + - @backstage/errors@1.1.5 + - @backstage/core-plugin-api@1.5.0 + - @backstage/catalog-model@1.2.1 + - @backstage/theme@0.2.18 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.6 + +## 1.2.0-next.2 + +### Patch Changes + +- 65454876fb2: Minor API report tweaks +- 3c96e77b513: Make scaffolder adhere to page themes by using page `fontColor` consistently. If your theme overwrites template list or card headers, review those styles. +- d9893263ba9: scaffolder/next: Fix for steps without properties +- Updated dependencies + - @backstage/core-components@0.12.5-next.2 + - @backstage/plugin-catalog-react@1.4.0-next.2 + - @backstage/core-plugin-api@1.5.0-next.2 + +## 1.2.0-next.1 + +### Minor Changes + +- 8f4d13f21cf: Move `useTaskStream`, `TaskBorder`, `TaskLogStream` and `TaskSteps` into `scaffolder-react`. + +### Patch Changes + +- 44941fc97eb: scaffolder/next: Move the `uiSchema` to its own property in the validation `context` to align with component development and access of `ui:options` +- Updated dependencies + - @backstage/core-components@0.12.5-next.1 + - @backstage/errors@1.1.5-next.0 + - @backstage/catalog-client@1.4.0-next.1 + - @backstage/core-plugin-api@1.4.1-next.1 + - @backstage/theme@0.2.18-next.0 + - @backstage/plugin-catalog-react@1.4.0-next.1 + - @backstage/catalog-model@1.2.1-next.1 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.6-next.1 + +## 1.1.1-next.0 + +### Patch Changes + +- c8d78b9ae9: fix bug with `hasErrors` returning false when dealing with empty objects +- 928a12a9b3: Internal refactor of `/alpha` exports. +- cc418d652a: scaffolder/next: Added the ability to get the fields definition in the schema in the validation function +- d4100d0ec4: Fix alignment bug for owners on `TemplateCard` +- Updated dependencies + - @backstage/catalog-client@1.4.0-next.0 + - @backstage/plugin-catalog-react@1.4.0-next.0 + - @backstage/core-plugin-api@1.4.1-next.0 + - @backstage/catalog-model@1.2.1-next.0 + - @backstage/core-components@0.12.5-next.0 + - @backstage/errors@1.1.4 + - @backstage/theme@0.2.17 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.6-next.0 + +## 1.1.0 + +### Minor Changes + +- a07750745b: Added `DescriptionField` field override to the `next/scaffolder` +- a521379688: Migrating the `TemplateEditorPage` to work with the new components from `@backstage/plugin-scaffolder-react` +- 8c2966536b: Embed scaffolder workflow in other components +- 5555e17313: refactor `createAsyncValidators` to be recursive to ensure validators are called in nested schemas. + +### Patch Changes + +- 04f717a8e1: `scaffolder/next`: bump `react-jsonschema-form` libraries to `v5-stable` +- b46f385eff: scaffolder/next: Implementing a simple `OngoingTask` page +- cbab8ac107: lock versions of `@rjsf/*-beta` packages +- 346d6b6630: Upgrade `@rjsf` version 5 dependencies to `beta.18` +- ccbf91051b: bump `@rjsf` `v5` dependencies to 5.1.0 +- d2ddde2108: Add `ScaffolderLayouts` to `NextScaffolderPage` +- Updated dependencies + - @backstage/core-components@0.12.4 + - @backstage/catalog-model@1.2.0 + - @backstage/theme@0.2.17 + - @backstage/core-plugin-api@1.4.0 + - @backstage/plugin-catalog-react@1.3.0 + - @backstage/catalog-client@1.3.1 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.5 + +## 1.1.0-next.2 + +### Minor Changes + +- 5555e17313: refactor `createAsyncValidators` to be recursive to ensure validators are called in nested schemas. + +### Patch Changes + +- b46f385eff: scaffolder/next: Implementing a simple `OngoingTask` page +- ccbf91051b: bump `@rjsf` `v5` dependencies to 5.1.0 +- Updated dependencies + - @backstage/catalog-model@1.2.0-next.1 + - @backstage/core-components@0.12.4-next.1 + - @backstage/catalog-client@1.3.1-next.1 + - @backstage/core-plugin-api@1.3.0 + - @backstage/errors@1.1.4 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-catalog-react@1.3.0-next.2 + - @backstage/plugin-scaffolder-common@1.2.5-next.1 + +## 1.1.0-next.1 + +### Patch Changes + +- 04f717a8e1: `scaffolder/next`: bump `react-jsonschema-form` libraries to `v5-stable` +- 346d6b6630: Upgrade `@rjsf` version 5 dependencies to `beta.18` +- Updated dependencies + - @backstage/core-components@0.12.4-next.0 + - @backstage/plugin-catalog-react@1.3.0-next.1 + - @backstage/catalog-client@1.3.1-next.0 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/core-plugin-api@1.3.0 + - @backstage/errors@1.1.4 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.5-next.0 + +## 1.1.0-next.0 + +### Minor Changes + +- 8c2966536b: Embed scaffolder workflow in other components + +### Patch Changes + +- cbab8ac107: lock versions of `@rjsf/*-beta` packages +- d2ddde2108: Add `ScaffolderLayouts` to `NextScaffolderPage` +- Updated dependencies + - @backstage/plugin-catalog-react@1.3.0-next.0 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/catalog-client@1.3.1-next.0 + - @backstage/plugin-scaffolder-common@1.2.5-next.0 + +## 1.0.0 + +### Major Changes + +- b4955ed7b9: Re-home some of the common types, components, hooks and `scaffolderApiRef` for the `@backstage/plugin-scaffolder` to this package for easy re-use across things that want to interact with the `scaffolder`. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.5 + - @backstage/plugin-scaffolder-common@1.2.4 + - @backstage/catalog-client@1.3.0 + - @backstage/plugin-catalog-react@1.2.4 + - @backstage/core-components@0.12.3 + - @backstage/core-plugin-api@1.3.0 + - @backstage/errors@1.1.4 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.0.0-next.0 + +### Major Changes + +- b4955ed7b9: Re-home some of the common types, components, hooks and `scaffolderApiRef` for the `@backstage/plugin-scaffolder` to this package for easy re-use across things that want to interact with the `scaffolder`. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.3.0-next.1 + - @backstage/catalog-client@1.3.0-next.2 + - @backstage/plugin-catalog-react@1.2.4-next.2 + - @backstage/catalog-model@1.1.5-next.1 + - @backstage/core-components@0.12.3-next.2 + - @backstage/errors@1.1.4 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.4-next.1 + in the review step label +- bcec60f: updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 1.8.6-next.0 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 0256f47d48..4460dfef50 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.8.6-next.0", + "version": "1.8.7-next.1", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library" diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 120d239ea1..5ba2e05768 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-scaffolder +## 1.20.2-next.1 + +### Patch Changes + +- 75dcd7e: Fixing bug in `formData` type as it should be `optional` as it's possibly undefined +- bcec60f: updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 1.20.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 9236784abd..1dbe7ad7b0 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.20.1-next.0", + "version": "1.20.2-next.1", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin" diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index 3cd732f2b9..4c47c9d55a 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-search-backend-module-catalog +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 0.1.25-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index 41828a7475..b1b1e0d643 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.1.25-next.0", + "version": "0.1.25-next.1", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 73aa7aee2c..228f390ddc 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 1.4.2-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 888b89e1bc..90ac189516 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.4.2-next.0", + "version": "1.4.2-next.1", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index 27137acc34..0649e48fcb 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-explore +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 0.1.25-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index 3e32174a2a..8abc027d57 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-explore", - "version": "0.1.25-next.0", + "version": "0.1.25-next.1", "description": "A module for the search backend that exports explore modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 9d4fd62f82..2344912758 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 0.5.28-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 899abcee31..e19cdc7559 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-pg", - "version": "0.5.28-next.0", + "version": "0.5.28-next.1", "description": "A module for the search backend that implements search using PostgreSQL", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md index 1d643c7180..e7f5cd560b 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 0.1.12-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index da22880943..e21c1c6a18 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-stack-overflow-collator", - "version": "0.1.12-next.0", + "version": "0.1.12-next.1", "description": "A module for the search backend that exports stack overflow modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index bd810f5208..9a05345cd8 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + ## 0.1.24-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 5df953cbf0..1b81340671 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.1.24-next.0", + "version": "0.1.24-next.1", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 769f8e5785..928a406cf9 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend-node +## 1.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 1.2.24-next.0 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 50f59089b2..f5ce41e840 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-node", - "version": "1.2.24-next.0", + "version": "1.2.24-next.1", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", "backstage": { "role": "node-library" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index c116515a7a..fa17e485c8 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend +## 1.5.10-next.1 + +### Patch Changes + +- 34dc47d: Move @backstage/repo-tools to devDependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-defaults@0.3.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 1.5.10-next.0 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 40371682fc..095c2d368d 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "1.5.10-next.0", + "version": "1.5.10-next.1", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin" diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 98c9852acc..e3455c41ab 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search +## 1.4.12-next.1 + +### Patch Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 1.4.12-next.0 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index c8a46d9555..1014036287 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.12-next.0", + "version": "1.4.12-next.1", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin" diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index e748bcd493..25b77a2261 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-signals-backend +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-signals-node@0.1.5-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 8a2ec3373b..5b87a03929 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.1.5-next.0", + "version": "0.1.5-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index ab160c0c8f..a596027808 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-signals-node +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index f93947ae4b..d1e130d01b 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-signals-node", "description": "Node.js library for the signals plugin", - "version": "0.1.5-next.0", + "version": "0.1.5-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 2764510f3c..cec699fcd7 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-techdocs-backend +## 1.10.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + ## 1.10.6-next.0 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index df8cd6e096..f96c6ec31c 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "1.10.6-next.0", + "version": "1.10.6-next.1", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin" diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index c7d0223508..00ad945353 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-techdocs-node +## 1.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 1.12.5-next.0 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 08b52b45b0..95ef28a2e0 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.12.5-next.0", + "version": "1.12.5-next.1", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library" diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 9402f0d688..915f283428 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-user-settings-backend +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.2.18-next.0 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index b3210d8357..38d6d0e04d 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.2.18-next.0", + "version": "0.2.18-next.1", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin"