From 794f11af0eaa7de1235d1d128abf8d9a7ca5a3e2 Mon Sep 17 00:00:00 2001 From: Ashraf Masarwa Date: Sun, 1 Mar 2026 12:39:09 +0200 Subject: [PATCH 1/4] Create Adminstrator Sidebar Menu --- workspaces/dcm/app-config.yaml | 17 + workspaces/dcm/examples/entities.yaml | 41 ++ workspaces/dcm/examples/org.yaml | 17 + .../template/content/catalog-info.yaml | 8 + .../dcm/examples/template/content/index.js | 17 + .../examples/template/content/package.json | 5 + .../dcm/examples/template/template.yaml | 74 +++ workspaces/dcm/package.json | 4 +- workspaces/dcm/packages/app/src/App.tsx | 1 + .../packages/app/src/components/Root/Root.tsx | 228 +++++++- workspaces/dcm/packages/backend/package.json | 3 + workspaces/dcm/packages/backend/src/index.ts | 9 +- .../dcm/plugins/dcm-backend/package.json | 4 + .../src/models/GetTokenResponse.ts | 24 + .../dcm-backend/src/models/RouterOptions.ts | 32 + .../dcm/plugins/dcm-backend/src/plugin.ts | 38 +- .../dcm/plugins/dcm-backend/src/router.ts | 56 +- .../plugins/dcm-backend/src/routes/access.ts | 50 ++ .../plugins/dcm-backend/src/routes/token.ts | 64 ++ .../plugins/dcm-backend/src/util/constant.ts | 17 + .../plugins/dcm-backend/src/util/tokenUtil.ts | 92 +++ .../dcm/plugins/dcm-common/package.json | 3 +- .../dcm/plugins/dcm-common/src/index.ts | 2 + .../dcm/plugins/dcm-common/src/permissions.ts | 26 + workspaces/dcm/yarn.lock | 545 +++++++++++++++++- 25 files changed, 1277 insertions(+), 100 deletions(-) create mode 100644 workspaces/dcm/examples/entities.yaml create mode 100644 workspaces/dcm/examples/org.yaml create mode 100644 workspaces/dcm/examples/template/content/catalog-info.yaml create mode 100644 workspaces/dcm/examples/template/content/index.js create mode 100644 workspaces/dcm/examples/template/content/package.json create mode 100644 workspaces/dcm/examples/template/template.yaml create mode 100644 workspaces/dcm/plugins/dcm-backend/src/models/GetTokenResponse.ts create mode 100644 workspaces/dcm/plugins/dcm-backend/src/models/RouterOptions.ts create mode 100644 workspaces/dcm/plugins/dcm-backend/src/routes/access.ts create mode 100644 workspaces/dcm/plugins/dcm-backend/src/routes/token.ts create mode 100644 workspaces/dcm/plugins/dcm-backend/src/util/constant.ts create mode 100644 workspaces/dcm/plugins/dcm-backend/src/util/tokenUtil.ts create mode 100644 workspaces/dcm/plugins/dcm-common/src/permissions.ts diff --git a/workspaces/dcm/app-config.yaml b/workspaces/dcm/app-config.yaml index 4b11226c3c..ac1c39ae7e 100644 --- a/workspaces/dcm/app-config.yaml +++ b/workspaces/dcm/app-config.yaml @@ -43,6 +43,23 @@ auth: providers: guest: {} +permission: + enabled: true + rbac: + policyFileReload: true + pluginsWithPermission: + - catalog + - scaffolder + - permission + - dcm + admin: + users: + - name: group:default/admins + - name: user:default/guest + superUsers: + - name: group:default/admins + - name: user:default/guest + scaffolder: {} catalog: diff --git a/workspaces/dcm/examples/entities.yaml b/workspaces/dcm/examples/entities.yaml new file mode 100644 index 0000000000..447e8b1f34 --- /dev/null +++ b/workspaces/dcm/examples/entities.yaml @@ -0,0 +1,41 @@ +--- +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system +apiVersion: backstage.io/v1alpha1 +kind: System +metadata: + name: examples +spec: + owner: guests +--- +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example-website +spec: + type: website + lifecycle: experimental + owner: guests + system: examples + providesApis: [example-grpc-api] +--- +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: example-grpc-api +spec: + type: grpc + lifecycle: experimental + owner: guests + system: examples + definition: | + syntax = "proto3"; + + service Exampler { + rpc Example (ExampleMessage) returns (ExampleMessage) {}; + } + + message ExampleMessage { + string example = 1; + }; diff --git a/workspaces/dcm/examples/org.yaml b/workspaces/dcm/examples/org.yaml new file mode 100644 index 0000000000..a10e81fc7f --- /dev/null +++ b/workspaces/dcm/examples/org.yaml @@ -0,0 +1,17 @@ +--- +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user +apiVersion: backstage.io/v1alpha1 +kind: User +metadata: + name: guest +spec: + memberOf: [guests] +--- +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group +apiVersion: backstage.io/v1alpha1 +kind: Group +metadata: + name: guests +spec: + type: team + children: [] diff --git a/workspaces/dcm/examples/template/content/catalog-info.yaml b/workspaces/dcm/examples/template/content/catalog-info.yaml new file mode 100644 index 0000000000..d4ccca42ef --- /dev/null +++ b/workspaces/dcm/examples/template/content/catalog-info.yaml @@ -0,0 +1,8 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: ${{ values.name | dump }} +spec: + type: service + owner: user:guest + lifecycle: experimental diff --git a/workspaces/dcm/examples/template/content/index.js b/workspaces/dcm/examples/template/content/index.js new file mode 100644 index 0000000000..d4e00fc5dc --- /dev/null +++ b/workspaces/dcm/examples/template/content/index.js @@ -0,0 +1,17 @@ +/* + * Copyright Red Hat, Inc. + * + * 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. + */ + +console.log('Hello from ${{ values.name }}!'); diff --git a/workspaces/dcm/examples/template/content/package.json b/workspaces/dcm/examples/template/content/package.json new file mode 100644 index 0000000000..86f968a73b --- /dev/null +++ b/workspaces/dcm/examples/template/content/package.json @@ -0,0 +1,5 @@ +{ + "name": "${{ values.name }}", + "private": true, + "dependencies": {} +} diff --git a/workspaces/dcm/examples/template/template.yaml b/workspaces/dcm/examples/template/template.yaml new file mode 100644 index 0000000000..33f262b49c --- /dev/null +++ b/workspaces/dcm/examples/template/template.yaml @@ -0,0 +1,74 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template +kind: Template +metadata: + name: example-nodejs-template + title: Example Node.js Template + description: An example template for the scaffolder that creates a simple Node.js service +spec: + owner: user:guest + type: service + + # These parameters are used to generate the input form in the frontend, and are + # used to gather input data for the execution of the template. + parameters: + - title: Fill in some steps + required: + - name + properties: + name: + title: Name + type: string + description: Unique name of the component + ui:autofocus: true + ui:options: + rows: 5 + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - github.com + + # These steps are executed in the scaffolder backend, using data that we gathered + # via the parameters above. + steps: + # Each step executes an action, in this case one templates files into the working directory. + - id: fetch-base + name: Fetch Base + action: fetch:template + input: + url: ./content + values: + name: ${{ parameters.name }} + + # This step publishes the contents of the working directory to GitHub. + - id: publish + name: Publish + action: publish:github + input: + allowedHosts: ['github.com'] + description: This is ${{ parameters.name }} + repoUrl: ${{ parameters.repoUrl }} + + # The final step is to register our new component in the catalog. + - id: register + name: Register + action: catalog:register + input: + repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' + + # Outputs are displayed to the user after a successful execution of the template. + output: + links: + - title: Repository + url: ${{ steps['publish'].output.remoteUrl }} + - title: Open in catalog + icon: catalog + entityRef: ${{ steps['register'].output.entityRef }} diff --git a/workspaces/dcm/package.json b/workspaces/dcm/package.json index 0b4e03fc2a..b77b4e105d 100644 --- a/workspaces/dcm/package.json +++ b/workspaces/dcm/package.json @@ -55,7 +55,9 @@ "resolutions": { "@types/react": "^18", "@types/react-dom": "^18", - "fsevents": "~2.3.2" + "fsevents": "~2.3.2", + "@backstage/backend-app-api": "1.4.1", + "@backstage/backend-plugin-api": "1.6.2" }, "prettier": "@spotify/prettier-config", "lint-staged": { diff --git a/workspaces/dcm/packages/app/src/App.tsx b/workspaces/dcm/packages/app/src/App.tsx index 423c3cae3d..13e01253ae 100644 --- a/workspaces/dcm/packages/app/src/App.tsx +++ b/workspaces/dcm/packages/app/src/App.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import React from 'react'; import { Navigate, Route } from 'react-router-dom'; import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; import { diff --git a/workspaces/dcm/packages/app/src/components/Root/Root.tsx b/workspaces/dcm/packages/app/src/components/Root/Root.tsx index c260bdf32f..0f6815eea7 100644 --- a/workspaces/dcm/packages/app/src/components/Root/Root.tsx +++ b/workspaces/dcm/packages/app/src/components/Root/Root.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ -import React, { type PropsWithChildren } from 'react'; -import { makeStyles } from '@material-ui/core'; +import React, { type PropsWithChildren, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { makeStyles, Button, Typography, Box } from '@material-ui/core'; import ExtensionIcon from '@material-ui/icons/Extension'; import HomeIcon from '@material-ui/icons/Home'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; @@ -38,10 +39,14 @@ import { } from '@backstage/core-components'; import MenuIcon from '@material-ui/icons/Menu'; import SearchIcon from '@material-ui/icons/Search'; +import SecurityIcon from '@material-ui/icons/Security'; +import StorageIcon from '@material-ui/icons/Storage'; +import VpnKeyIcon from '@material-ui/icons/VpnKey'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; import { useRhdhTheme } from '../../hooks/useRhdhTheme'; -import { Administration } from '@backstage-community/plugin-rbac'; const useSidebarLogoStyles = makeStyles({ root: { @@ -58,6 +63,131 @@ const useSidebarLogoStyles = makeStyles({ }, }); +const useSidebarItemStyles = makeStyles(theme => ({ + '.MuiButtonBase-root': {}, + securityIcon: { + '& .securityIcon': { + fontSize: '20px !important', + minWidth: 'unset !important', + width: '20px !important', + height: '20px !important', + }, + }, + submenuItem: { + marginBottom: 4, + '& .MuiSvgIcon-root': { + fontSize: '20px !important', + }, + '& .MuiTypography-root': { + marginLeft: 8, + fontWeight: 400, + fontSize: 14, + }, + }, + submenuItemActive: { + borderRadius: 6, + border: `1px solid ${ + (theme.palette as { type?: string; mode?: string }).type === 'dark' || + (theme.palette as { mode?: string }).mode === 'dark' + ? 'rgba(255,255,255,0.2)' + : 'rgba(0,0,0,0.12)' + }`, + backgroundColor: `${ + (theme.palette as { type?: string; mode?: string }).type === 'dark' || + (theme.palette as { mode?: string }).mode === 'dark' + ? 'rgba(255,255,255,0.12)' + : 'rgba(0,0,0,0.06)' + } !important`, + }, + inactiveItem: { + backgroundColor: 'transparent !important', + }, + iconContainer: { + width: 20, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + '& .MuiSvgIcon-root': { fontSize: '20px !important' }, + marginRight: 5, + }, + text: { + marginRight: 20, + }, + collapsibleTrigger: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + minHeight: 48, + padding: '0 !important', + marginLeft: 0, + marginBottom: 10, + textTransform: 'none', + color: `${ + theme.palette.navigation?.color ?? + ((theme.palette as { type?: string; mode?: string }).type === 'dark' || + (theme.palette as { type?: string; mode?: string }).mode === 'dark' + ? theme.palette.common.white + : theme.palette.text.primary) + } !important`, + '& .MuiTypography-root': { + color: 'inherit !important', + marginLeft: theme.spacing(0.5), + fontSize: 14, + fontWeight: 600, + }, + '& .MuiSvgIcon-root': { + fontSize: '20px !important', + flexShrink: 0, + }, + '&:hover': { + backgroundColor: + theme.palette.navigation?.navItem?.hoverBackground ?? + 'rgba(255,255,255,0.08)', + }, + }, + collapsibleContent: { + marginLeft: theme.spacing(3), + }, +})); + +const CollapsibleSubmenu = ({ + icon, + text, + children, + classes, +}: { + icon: React.ReactElement; + text: string; + children: React.ReactNode; + classes: { + collapsibleTrigger: string; + collapsibleContent: string; + iconContainer: string; + text: string; + }; +}) => { + const [isOpen, setIsOpen] = useState(true); + const { isOpen: sidebarOpen } = useSidebarOpenState(); + + return ( + + + {isOpen && {children}} + + ); +}; + const Logo = (props: { isOpen?: boolean }) => { const { isOpen = false } = props; const rhdhTheme = useRhdhTheme(); @@ -87,33 +217,67 @@ const SidebarLogo = () => { ); }; -export const Root = ({ children }: PropsWithChildren<{}>) => ( - - - - } to="/search"> - - - - }> - - - - +export const Root = ({ children }: PropsWithChildren<{}>) => { + const classes = useSidebarItemStyles(); + const location = useLocation(); + const isDcmActive = location.pathname === '/dcm'; + const isRbacActive = location.pathname === '/rbac'; + + return ( + + + + } to="/search"> + + - - - - - - } - to="/settings" - > - - - - {children} - -); + }> + + + + + + + + }> + + } + text="Administration" + classes={classes} + > + + + + + } + to="/settings" + > + + + + {children} + + ); +}; diff --git a/workspaces/dcm/packages/backend/package.json b/workspaces/dcm/packages/backend/package.json index 24984fc537..e417d06e99 100644 --- a/workspaces/dcm/packages/backend/package.json +++ b/workspaces/dcm/packages/backend/package.json @@ -20,6 +20,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { + "@backstage-community/plugin-rbac-backend": "5.2.6", "@backstage/backend-defaults": "^0.15.1", "@backstage/config": "^1.3.2", "@backstage/plugin-app-backend": "^0.4.4", @@ -28,6 +29,8 @@ "@backstage/plugin-catalog-backend": "^1.30.0", "@backstage/plugin-catalog-backend-module-logs": "^0.1.8", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.4", + "@backstage/plugin-events-backend": "^0.5.0", + "@backstage/plugin-permission-backend": "^0.5.53", "@backstage/plugin-proxy-backend": "^0.5.10", "@backstage/plugin-scaffolder-backend": "^1.29.0", "@backstage/plugin-search-backend": "^1.8.1", diff --git a/workspaces/dcm/packages/backend/src/index.ts b/workspaces/dcm/packages/backend/src/index.ts index 4e469731b2..66a38f6831 100644 --- a/workspaces/dcm/packages/backend/src/index.ts +++ b/workspaces/dcm/packages/backend/src/index.ts @@ -23,17 +23,22 @@ backend.add(import('@backstage/plugin-proxy-backend')); backend.add(import('@backstage/plugin-scaffolder-backend')); backend.add(import('@backstage/plugin-techdocs-backend')); -backend.add(import('@red-hat-developer-hub/backstage-plugin-dcm-backend')); - backend.add(import('@backstage/plugin-auth-backend')); backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); +backend.add(import('@backstage/plugin-events-backend')); + backend.add(import('@backstage/plugin-catalog-backend')); backend.add( import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ); backend.add(import('@backstage/plugin-catalog-backend-module-logs')); +// RBAC backend (registers as "permission" and provides /rbac; do not add plugin-permission-backend separately) +backend.add(import('@backstage-community/plugin-rbac-backend')); + +backend.add(import('@red-hat-developer-hub/backstage-plugin-dcm-backend')); + backend.add(import('@backstage/plugin-search-backend')); backend.add(import('@backstage/plugin-search-backend-module-catalog')); backend.add(import('@backstage/plugin-search-backend-module-techdocs')); diff --git a/workspaces/dcm/plugins/dcm-backend/package.json b/workspaces/dcm/plugins/dcm-backend/package.json index e62af2c614..8bcc35287b 100644 --- a/workspaces/dcm/plugins/dcm-backend/package.json +++ b/workspaces/dcm/plugins/dcm-backend/package.json @@ -38,7 +38,11 @@ "@backstage/catalog-client": "^1.12.1", "@backstage/errors": "^1.2.7", "@backstage/plugin-catalog-node": "^1.20.1", + "@backstage/plugin-permission-common": "^0.8.4", + "@backstage/plugin-permission-node": "^0.8.7", "@backstage/types": "^1.2.2", + "@red-hat-developer-hub/backstage-plugin-dcm-common": "workspace:^", + "assert": "^2.1.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", "zod": "^3.25.76" diff --git a/workspaces/dcm/plugins/dcm-backend/src/models/GetTokenResponse.ts b/workspaces/dcm/plugins/dcm-backend/src/models/GetTokenResponse.ts new file mode 100644 index 0000000000..db0052abcb --- /dev/null +++ b/workspaces/dcm/plugins/dcm-backend/src/models/GetTokenResponse.ts @@ -0,0 +1,24 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 THE License, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @public + */ +export interface GetTokenResponse { + accessToken: string; + /** The Unix Epoch at which the token will expire */ + expiresAt: number; +} diff --git a/workspaces/dcm/plugins/dcm-backend/src/models/RouterOptions.ts b/workspaces/dcm/plugins/dcm-backend/src/models/RouterOptions.ts new file mode 100644 index 0000000000..9f393a75ab --- /dev/null +++ b/workspaces/dcm/plugins/dcm-backend/src/models/RouterOptions.ts @@ -0,0 +1,32 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 THE License, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + LoggerService, + RootConfigService, + HttpAuthService, + PermissionsService, + CacheService, +} from '@backstage/backend-plugin-api'; + +/** @public */ +export interface RouterOptions { + logger: LoggerService; + config?: RootConfigService; + httpAuth: HttpAuthService; + permissions: PermissionsService; + cache: CacheService; +} diff --git a/workspaces/dcm/plugins/dcm-backend/src/plugin.ts b/workspaces/dcm/plugins/dcm-backend/src/plugin.ts index 4f98efe8f3..387381284c 100644 --- a/workspaces/dcm/plugins/dcm-backend/src/plugin.ts +++ b/workspaces/dcm/plugins/dcm-backend/src/plugin.ts @@ -9,7 +9,7 @@ * * 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. + * WITHOUT WARRANTIES OR CONDITIONS OF THE License, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @@ -18,7 +18,6 @@ import { createBackendPlugin, } from '@backstage/backend-plugin-api'; import { createRouter } from './router'; -import { todoListServiceRef } from './services/TodoListService'; /** * dcmPlugin backend plugin @@ -30,17 +29,34 @@ export const dcmPlugin = createBackendPlugin({ register(env) { env.registerInit({ deps: { - httpAuth: coreServices.httpAuth, httpRouter: coreServices.httpRouter, - todoList: todoListServiceRef, + logger: coreServices.logger, + config: coreServices.rootConfig, + httpAuth: coreServices.httpAuth, + permissions: coreServices.permissions, + cache: coreServices.cache, }, - async init({ httpAuth, httpRouter, todoList }) { - httpRouter.use( - await createRouter({ - httpAuth, - todoList, - }), - ); + async init({ httpRouter, logger, config, httpAuth, permissions, cache }) { + const router = await createRouter({ + logger, + config, + httpAuth, + permissions, + cache, + }); + httpRouter.use(router); + httpRouter.addAuthPolicy({ + path: '/health', + allow: 'unauthenticated', + }); + httpRouter.addAuthPolicy({ + path: '/token', + allow: 'user-cookie', + }); + httpRouter.addAuthPolicy({ + path: '/access', + allow: 'user-cookie', + }); }, }); }, diff --git a/workspaces/dcm/plugins/dcm-backend/src/router.ts b/workspaces/dcm/plugins/dcm-backend/src/router.ts index ea01d6559b..3e0511197a 100644 --- a/workspaces/dcm/plugins/dcm-backend/src/router.ts +++ b/workspaces/dcm/plugins/dcm-backend/src/router.ts @@ -9,59 +9,37 @@ * * 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. + * WITHOUT WARRANTIES OR CONDITIONS OF THE License, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-disable @backstage/no-undeclared-imports -- deps in dcm-backend package.json */ -import { HttpAuthService } from '@backstage/backend-plugin-api'; -import { InputError } from '@backstage/errors'; -import { z } from 'zod'; +import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; +import { dcmPluginPermissions } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; import express from 'express'; import Router from 'express-promise-router'; -import { todoListServiceRef } from './services/TodoListService'; +import type { RouterOptions } from './models/RouterOptions'; +import { getToken } from './routes/token'; +import { getAccess } from './routes/access'; -export async function createRouter({ - httpAuth, - todoList, -}: { - httpAuth: HttpAuthService; - todoList: typeof todoListServiceRef.T; -}): Promise { +export async function createRouter( + options: RouterOptions, +): Promise { const router = Router(); - router.use(express.json()); - - // TEMPLATE NOTE: - // Zod is a powerful library for data validation and recommended in particular - // for user-defined schemas. In this case we use it for input validation too. - // - // If you want to define a schema for your API we recommend using Backstage's - // OpenAPI tooling: https://backstage.io/docs/next/openapi/01-getting-started - const todoSchema = z.object({ - title: z.string(), - entityRef: z.string().optional(), - }); - router.post('/todos', async (req, res) => { - const parsed = todoSchema.safeParse(req.body); - if (!parsed.success) { - throw new InputError(parsed.error.toString()); - } - - const result = await todoList.createTodo(parsed.data, { - credentials: await httpAuth.credentials(req, { allow: ['user'] }), - }); + router.use(express.json()); - res.status(201).json(result); + const permissionsIntegrationRouter = createPermissionIntegrationRouter({ + permissions: dcmPluginPermissions, }); + router.use(permissionsIntegrationRouter); - router.get('/todos', async (_req, res) => { - res.json(await todoList.listTodos()); + router.get('/health', (_req, res) => { + res.json({ status: 'ok' }); }); - router.get('/todos/:id', async (req, res) => { - res.json(await todoList.getTodo({ id: req.params.id })); - }); + router.get('/token', getToken(options)); + router.get('/access', getAccess(options)); return router; } diff --git a/workspaces/dcm/plugins/dcm-backend/src/routes/access.ts b/workspaces/dcm/plugins/dcm-backend/src/routes/access.ts new file mode 100644 index 0000000000..44976a8244 --- /dev/null +++ b/workspaces/dcm/plugins/dcm-backend/src/routes/access.ts @@ -0,0 +1,50 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 THE License, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { RequestHandler } from 'express'; +import type { RouterOptions } from '../models/RouterOptions'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { dcmPluginPermissions } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; + +async function authorize( + request: Parameters[0], + options: RouterOptions, +): Promise { + const { permissions, httpAuth } = options; + const credentials = await httpAuth.credentials(request); + const decisions = await permissions.authorize( + dcmPluginPermissions.map(permission => ({ permission })), + { credentials }, + ); + const allow = decisions.find(d => d.result === AuthorizeResult.ALLOW); + return allow ? AuthorizeResult.ALLOW : AuthorizeResult.DENY; +} + +export const getAccess = + (options: RouterOptions): RequestHandler => + async (req, response) => { + const { logger } = options; + + const decision = await authorize(req, options); + + logger.info('DCM access decision:', decision); + + response.json({ + decision, + authorizeClusterIds: [], + authorizeProjects: [], + }); + }; diff --git a/workspaces/dcm/plugins/dcm-backend/src/routes/token.ts b/workspaces/dcm/plugins/dcm-backend/src/routes/token.ts new file mode 100644 index 0000000000..1d049ddaba --- /dev/null +++ b/workspaces/dcm/plugins/dcm-backend/src/routes/token.ts @@ -0,0 +1,64 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 THE License, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { RequestHandler } from 'express'; +import type { GetTokenResponse } from '../models/GetTokenResponse'; +import type { RouterOptions } from '../models/RouterOptions'; +import { DEFAULT_SSO_BASE_URL } from '../util/constant'; +import assert from 'assert'; + +export const getToken = + (options: RouterOptions): RequestHandler => + async (_, response) => { + const { logger, config } = options; + + assert(typeof config !== 'undefined', 'Config is undefined'); + + logger.info('Requesting new access token'); + + const ssoBaseUrl = + config.getOptionalString('dcm.ssoBaseUrl') ?? DEFAULT_SSO_BASE_URL; + const clientId = config.getString('dcm.clientId'); + const clientSecret = config.getString('dcm.clientSecret'); + + const tokenUrl = `${ssoBaseUrl}/auth/realms/redhat-external/protocol/openid-connect/token`; + const body = new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + scope: 'api.console', + grant_type: 'client_credentials', + }); + + const rhSsoResponse = await fetch(tokenUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: body.toString(), + }); + + if (rhSsoResponse.ok) { + const json = (await rhSsoResponse.json()) as { + access_token: string; + expires_in: number; + }; + const bodyRes: GetTokenResponse = { + accessToken: json.access_token, + expiresAt: Date.now() + json.expires_in * 1000, + }; + response.json(bodyRes); + } else { + throw new Error(rhSsoResponse.statusText); + } + }; diff --git a/workspaces/dcm/plugins/dcm-backend/src/util/constant.ts b/workspaces/dcm/plugins/dcm-backend/src/util/constant.ts new file mode 100644 index 0000000000..93397df581 --- /dev/null +++ b/workspaces/dcm/plugins/dcm-backend/src/util/constant.ts @@ -0,0 +1,17 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 THE License, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const DEFAULT_SSO_BASE_URL = 'https://sso.redhat.com'; diff --git a/workspaces/dcm/plugins/dcm-backend/src/util/tokenUtil.ts b/workspaces/dcm/plugins/dcm-backend/src/util/tokenUtil.ts new file mode 100644 index 0000000000..4ee0b02cc4 --- /dev/null +++ b/workspaces/dcm/plugins/dcm-backend/src/util/tokenUtil.ts @@ -0,0 +1,92 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 THE License, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import assert from 'assert'; +import type { RouterOptions } from '../models/RouterOptions'; +import { DEFAULT_SSO_BASE_URL } from './constant'; + +const TOKEN_CACHE_KEY = 'dcm_sso_access_token'; + +export const getTokenFromApi = async ( + options: RouterOptions, +): Promise => { + const { logger, config, cache } = options; + + const now = Date.now(); + + const cachedToken = (await cache.get(TOKEN_CACHE_KEY)) as + | { token: string; expiresAt: number } + | undefined; + + if (cachedToken) { + const timeUntilExpirySeconds = Math.floor( + (cachedToken.expiresAt - now) / 1000, + ); + logger.info( + `Cache check: Token expires in ${timeUntilExpirySeconds}s, needs >60s to be valid`, + ); + } else { + logger.info('Cache check: No cached token exists'); + } + + if (cachedToken && cachedToken.expiresAt > now + 60000) { + logger.info('Using cached access token'); + return cachedToken.token; + } + + assert(typeof config !== 'undefined', 'Config is undefined'); + + logger.info('Requesting new access token'); + + const ssoBaseUrl = + config.getOptionalString('dcm.ssoBaseUrl') ?? DEFAULT_SSO_BASE_URL; + const clientId = config.getString('dcm.clientId'); + const clientSecret = config.getString('dcm.clientSecret'); + + const tokenUrl = `${ssoBaseUrl}/auth/realms/redhat-external/protocol/openid-connect/token`; + const body = new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + scope: 'api.console', + grant_type: 'client_credentials', + }); + + const response = await fetch(tokenUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: body.toString(), + }); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = (await response.json()) as { + access_token: string; + expires_in: number; + }; + const accessToken = json.access_token; + const expiresAt = Date.now() + json.expires_in * 1000; + + await cache.set( + TOKEN_CACHE_KEY, + { token: accessToken, expiresAt }, + { ttl: json.expires_in * 1000 }, + ); + + logger.info(`Token cached, expires in ${json.expires_in} seconds`); + return accessToken; +}; diff --git a/workspaces/dcm/plugins/dcm-common/package.json b/workspaces/dcm/plugins/dcm-common/package.json index 9d19c6c1b3..f74c32ebbf 100644 --- a/workspaces/dcm/plugins/dcm-common/package.json +++ b/workspaces/dcm/plugins/dcm-common/package.json @@ -46,7 +46,8 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/core-plugin-api": "^1.12.2" + "@backstage/core-plugin-api": "^1.12.2", + "@backstage/plugin-permission-common": "^0.8.4" }, "devDependencies": { "@backstage/cli": "^0.35.2" diff --git a/workspaces/dcm/plugins/dcm-common/src/index.ts b/workspaces/dcm/plugins/dcm-common/src/index.ts index 3b52c918b6..561858466d 100644 --- a/workspaces/dcm/plugins/dcm-common/src/index.ts +++ b/workspaces/dcm/plugins/dcm-common/src/index.ts @@ -21,3 +21,5 @@ * @public */ export const DCM_COMMON_PLUGIN_ID = 'dcm' as const; + +export { dcmPluginReadPermission, dcmPluginPermissions } from './permissions'; diff --git a/workspaces/dcm/plugins/dcm-common/src/permissions.ts b/workspaces/dcm/plugins/dcm-common/src/permissions.ts new file mode 100644 index 0000000000..a179bd268f --- /dev/null +++ b/workspaces/dcm/plugins/dcm-common/src/permissions.ts @@ -0,0 +1,26 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 THE License, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createPermission } from '@backstage/plugin-permission-common'; + +/** @public */ +export const dcmPluginReadPermission = createPermission({ + name: 'dcm.plugin', + attributes: { action: 'read' }, +}); + +/** @public */ +export const dcmPluginPermissions = [dcmPluginReadPermission]; diff --git a/workspaces/dcm/yarn.lock b/workspaces/dcm/yarn.lock index 6b5382fd64..5350143d6a 100644 --- a/workspaces/dcm/yarn.lock +++ b/workspaces/dcm/yarn.lock @@ -1569,6 +1569,35 @@ __metadata: languageName: node linkType: hard +"@backstage-community/plugin-rbac-backend@npm:5.2.6": + version: 5.2.6 + resolution: "@backstage-community/plugin-rbac-backend@npm:5.2.6" + dependencies: + "@backstage-community/plugin-rbac-common": "npm:^1.12.2" + "@backstage-community/plugin-rbac-node": "npm:^1.8.2" + "@backstage/backend-defaults": "npm:^0.5.2" + "@backstage/backend-plugin-api": "npm:^1.0.1" + "@backstage/catalog-client": "npm:^1.7.1" + "@backstage/catalog-model": "npm:^1.7.0" + "@backstage/errors": "npm:^1.2.4" + "@backstage/plugin-auth-node": "npm:^0.5.3" + "@backstage/plugin-permission-backend": "npm:^0.5.50" + "@backstage/plugin-permission-common": "npm:^0.8.1" + "@backstage/plugin-permission-node": "npm:^0.8.4" + "@dagrejs/graphlib": "npm:^2.1.13" + "@janus-idp/backstage-plugin-audit-log-node": "npm:^1.7.1" + casbin: "npm:^5.27.1" + chokidar: "npm:^3.6.0" + csv-parse: "npm:^5.5.5" + express: "npm:^4.18.2" + js-yaml: "npm:^4.1.0" + knex: "npm:^3.0.0" + lodash: "npm:^4.17.21" + typeorm-adapter: "npm:^1.6.1" + checksum: 10/095da0a1f6922bf635ea89c7a4ba9d9decb3dab58b43bc34694fff5db912a42280d061845348c1a4af6b0ef38cb694d54b7c24225f3416cc126433605e2d70ea + languageName: node + linkType: hard + "@backstage-community/plugin-rbac-common@npm:^1.12.2": version: 1.23.0 resolution: "@backstage-community/plugin-rbac-common@npm:1.23.0" @@ -1579,6 +1608,15 @@ __metadata: languageName: node linkType: hard +"@backstage-community/plugin-rbac-node@npm:^1.8.2": + version: 1.17.0 + resolution: "@backstage-community/plugin-rbac-node@npm:1.17.0" + dependencies: + "@backstage/backend-plugin-api": "npm:^1.6.2" + checksum: 10/60a3e2f1757d9393a231ea0f518b7b01aa94bdda76809dd4df4aebab12ae445c5d53806623c59cd1b5198c21a7681794fb20d4061a780c89e1ca688a6995c66d + languageName: node + linkType: hard + "@backstage-community/plugin-rbac@npm:1.33.2": version: 1.33.2 resolution: "@backstage-community/plugin-rbac@npm:1.33.2" @@ -1634,7 +1672,7 @@ __metadata: languageName: node linkType: hard -"@backstage/backend-app-api@npm:^1.2.1, @backstage/backend-app-api@npm:^1.2.3, @backstage/backend-app-api@npm:^1.4.1": +"@backstage/backend-app-api@npm:1.4.1": version: 1.4.1 resolution: "@backstage/backend-app-api@npm:1.4.1" dependencies: @@ -1888,6 +1926,83 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-defaults@npm:^0.5.2": + version: 0.5.3 + resolution: "@backstage/backend-defaults@npm:0.5.3" + dependencies: + "@aws-sdk/abort-controller": "npm:^3.347.0" + "@aws-sdk/client-codecommit": "npm:^3.350.0" + "@aws-sdk/client-s3": "npm:^3.350.0" + "@aws-sdk/credential-providers": "npm:^3.350.0" + "@aws-sdk/types": "npm:^3.347.0" + "@backstage/backend-app-api": "npm:^1.0.2" + "@backstage/backend-dev-utils": "npm:^0.1.5" + "@backstage/backend-plugin-api": "npm:^1.0.2" + "@backstage/cli-common": "npm:^0.1.15" + "@backstage/cli-node": "npm:^0.2.10" + "@backstage/config": "npm:^1.3.0" + "@backstage/config-loader": "npm:^1.9.2" + "@backstage/errors": "npm:^1.2.5" + "@backstage/integration": "npm:^1.15.2" + "@backstage/integration-aws-node": "npm:^0.1.13" + "@backstage/plugin-auth-node": "npm:^0.5.4" + "@backstage/plugin-events-node": "npm:^0.4.5" + "@backstage/plugin-permission-node": "npm:^0.8.5" + "@backstage/types": "npm:^1.2.0" + "@google-cloud/storage": "npm:^7.0.0" + "@keyv/memcache": "npm:^1.3.5" + "@keyv/redis": "npm:^2.5.3" + "@manypkg/get-packages": "npm:^1.1.3" + "@octokit/rest": "npm:^19.0.3" + "@opentelemetry/api": "npm:^1.3.0" + "@types/cors": "npm:^2.8.6" + "@types/express": "npm:^4.17.6" + archiver: "npm:^7.0.0" + base64-stream: "npm:^1.0.0" + better-sqlite3: "npm:^11.0.0" + compression: "npm:^1.7.4" + concat-stream: "npm:^2.0.0" + cookie: "npm:^0.7.0" + cors: "npm:^2.8.5" + cron: "npm:^3.0.0" + express: "npm:^4.17.1" + express-promise-router: "npm:^4.1.0" + fs-extra: "npm:^11.2.0" + git-url-parse: "npm:^15.0.0" + helmet: "npm:^6.0.0" + isomorphic-git: "npm:^1.23.0" + jose: "npm:^5.0.0" + keyv: "npm:^4.5.2" + knex: "npm:^3.0.0" + lodash: "npm:^4.17.21" + logform: "npm:^2.3.2" + luxon: "npm:^3.0.0" + minimatch: "npm:^9.0.0" + minimist: "npm:^1.2.5" + morgan: "npm:^1.10.0" + mysql2: "npm:^3.0.0" + node-fetch: "npm:^2.7.0" + node-forge: "npm:^1.3.1" + p-limit: "npm:^3.1.0" + path-to-regexp: "npm:^8.0.0" + pg: "npm:^8.11.3" + pg-connection-string: "npm:^2.3.0" + pg-format: "npm:^1.0.4" + raw-body: "npm:^2.4.1" + selfsigned: "npm:^2.0.0" + stoppable: "npm:^1.1.0" + tar: "npm:^6.1.12" + triple-beam: "npm:^1.4.1" + uuid: "npm:^11.0.0" + winston: "npm:^3.2.1" + winston-transport: "npm:^4.5.0" + yauzl: "npm:^3.0.0" + yn: "npm:^4.0.0" + zod: "npm:^3.22.4" + checksum: 10/b276ca8716835f68bf365b0d99f8b3ffd02609daf6c5fc6f750de8dadcac9f26e4740cfb4d44886d687f87e5820060efa560ee74eba08b863956a521fbe59562 + languageName: node + linkType: hard + "@backstage/backend-defaults@npm:^0.8.0": version: 0.8.2 resolution: "@backstage/backend-defaults@npm:0.8.2" @@ -2022,7 +2137,31 @@ __metadata: languageName: node linkType: hard -"@backstage/backend-plugin-api@npm:^1.0.0, @backstage/backend-plugin-api@npm:^1.1.1, @backstage/backend-plugin-api@npm:^1.2.0, @backstage/backend-plugin-api@npm:^1.2.1, @backstage/backend-plugin-api@npm:^1.3.0, @backstage/backend-plugin-api@npm:^1.3.1, @backstage/backend-plugin-api@npm:^1.4.1, @backstage/backend-plugin-api@npm:^1.4.4, @backstage/backend-plugin-api@npm:^1.5.0, @backstage/backend-plugin-api@npm:^1.6.0, @backstage/backend-plugin-api@npm:^1.6.1, @backstage/backend-plugin-api@npm:^1.6.2": +"@backstage/backend-openapi-utils@npm:^0.6.6": + version: 0.6.6 + resolution: "@backstage/backend-openapi-utils@npm:0.6.6" + dependencies: + "@apidevtools/swagger-parser": "npm:^10.1.0" + "@backstage/backend-plugin-api": "npm:^1.7.0" + "@backstage/errors": "npm:^1.2.7" + "@backstage/types": "npm:^1.2.2" + "@types/express": "npm:^4.17.6" + "@types/express-serve-static-core": "npm:^4.17.5" + ajv: "npm:^8.16.0" + express: "npm:^4.22.0" + express-openapi-validator: "npm:^5.5.8" + express-promise-router: "npm:^4.1.0" + get-port: "npm:^5.1.1" + json-schema-to-ts: "npm:^3.0.0" + lodash: "npm:^4.17.21" + mockttp: "npm:^3.13.0" + openapi-merge: "npm:^1.3.2" + openapi3-ts: "npm:^3.1.2" + checksum: 10/586006ad050233955cc702701e6d3437ca26289ca1202162738b26bc18707050d30eb3f6ec5507752003d8c1e171567e6022d01b06774cd28f13562850b38a8d + languageName: node + linkType: hard + +"@backstage/backend-plugin-api@npm:1.6.2": version: 1.6.2 resolution: "@backstage/backend-plugin-api@npm:1.6.2" dependencies: @@ -2096,6 +2235,20 @@ __metadata: languageName: node linkType: hard +"@backstage/catalog-client@npm:^1.7.1": + version: 1.13.0 + resolution: "@backstage/catalog-client@npm:1.13.0" + dependencies: + "@backstage/catalog-model": "npm:^1.7.6" + "@backstage/errors": "npm:^1.2.7" + "@backstage/filter-predicates": "npm:^0.1.0" + cross-fetch: "npm:^4.0.0" + lodash: "npm:^4.17.21" + uri-template: "npm:^2.0.0" + checksum: 10/a89ef429d573e8eb8b8e5aef0ed0004e7a00c1c46567710f4ef436508fe1a9bc818cd6f5c77f8d055663cecf33ccc6c0dbcfd8b9b39f591ce0fb0d34a73dfa41 + languageName: node + linkType: hard + "@backstage/catalog-model@npm:^1.7.0, @backstage/catalog-model@npm:^1.7.3, @backstage/catalog-model@npm:^1.7.4, @backstage/catalog-model@npm:^1.7.5, @backstage/catalog-model@npm:^1.7.6": version: 1.7.6 resolution: "@backstage/catalog-model@npm:1.7.6" @@ -2120,6 +2273,34 @@ __metadata: languageName: node linkType: hard +"@backstage/cli-common@npm:^0.1.15, @backstage/cli-common@npm:^0.1.18": + version: 0.1.18 + resolution: "@backstage/cli-common@npm:0.1.18" + dependencies: + "@backstage/errors": "npm:^1.2.7" + cross-spawn: "npm:^7.0.3" + global-agent: "npm:^3.0.0" + undici: "npm:^7.2.3" + checksum: 10/2ed0c51bfc7a24d09a2c5fdb0e9b715f654e1ec5b6f6d528c457dad96ad57dfc57840292712dac29a9af206bb4f9d3cefbe311200f5708f9587386b00897e5b0 + languageName: node + linkType: hard + +"@backstage/cli-node@npm:^0.2.10": + version: 0.2.18 + resolution: "@backstage/cli-node@npm:0.2.18" + dependencies: + "@backstage/cli-common": "npm:^0.1.18" + "@backstage/errors": "npm:^1.2.7" + "@backstage/types": "npm:^1.2.2" + "@manypkg/get-packages": "npm:^1.1.3" + "@yarnpkg/parsers": "npm:^3.0.0" + fs-extra: "npm:^11.2.0" + semver: "npm:^7.5.3" + zod: "npm:^3.25.76" + checksum: 10/fafdacdf29e8aac9607c5dd2de3a4294f2d40acc5e610e6041841d678fb460b4fff74f39824f6f155fc5191f09ed6537e4e2c867bc2708855ce0a4d7ea62e3a3 + languageName: node + linkType: hard + "@backstage/cli-node@npm:^0.2.13, @backstage/cli-node@npm:^0.2.17": version: 0.2.17 resolution: "@backstage/cli-node@npm:0.2.17" @@ -2305,7 +2486,30 @@ __metadata: languageName: node linkType: hard -"@backstage/config@npm:^1.2.0, @backstage/config@npm:^1.3.2, @backstage/config@npm:^1.3.3, @backstage/config@npm:^1.3.5, @backstage/config@npm:^1.3.6": +"@backstage/config-loader@npm:^1.9.2": + version: 1.10.8 + resolution: "@backstage/config-loader@npm:1.10.8" + dependencies: + "@backstage/cli-common": "npm:^0.1.18" + "@backstage/config": "npm:^1.3.6" + "@backstage/errors": "npm:^1.2.7" + "@backstage/types": "npm:^1.2.2" + "@types/json-schema": "npm:^7.0.6" + ajv: "npm:^8.10.0" + chokidar: "npm:^3.5.2" + fs-extra: "npm:^11.2.0" + json-schema: "npm:^0.4.0" + json-schema-merge-allof: "npm:^0.8.1" + json-schema-traverse: "npm:^1.0.0" + lodash: "npm:^4.17.21" + minimist: "npm:^1.2.5" + typescript-json-schema: "npm:^0.67.0" + yaml: "npm:^2.0.0" + checksum: 10/c44e6796ce0a2efe882f7a69f32f9e1f00cd6dca3406f0bd3a96ab22e7f7561b25154cb1cee208b069a97056d6e7728cf00e73f0ba15993ffa1985c75dc604d8 + languageName: node + linkType: hard + +"@backstage/config@npm:^1.2.0, @backstage/config@npm:^1.3.0, @backstage/config@npm:^1.3.2, @backstage/config@npm:^1.3.3, @backstage/config@npm:^1.3.5, @backstage/config@npm:^1.3.6": version: 1.3.6 resolution: "@backstage/config@npm:1.3.6" dependencies: @@ -2672,7 +2876,7 @@ __metadata: languageName: node linkType: hard -"@backstage/errors@npm:^1.2.4, @backstage/errors@npm:^1.2.7": +"@backstage/errors@npm:^1.2.4, @backstage/errors@npm:^1.2.5, @backstage/errors@npm:^1.2.7": version: 1.2.7 resolution: "@backstage/errors@npm:1.2.7" dependencies: @@ -2692,6 +2896,19 @@ __metadata: languageName: node linkType: hard +"@backstage/filter-predicates@npm:^0.1.0": + version: 0.1.0 + resolution: "@backstage/filter-predicates@npm:0.1.0" + dependencies: + "@backstage/config": "npm:^1.3.6" + "@backstage/errors": "npm:^1.2.7" + "@backstage/types": "npm:^1.2.2" + zod: "npm:^3.25.76" + zod-validation-error: "npm:^4.0.2" + checksum: 10/82bd6dde6615e48a7152fbdb6b3d2fa64b574864edd5d9e00b52922e120237c42822afc601a693c81af28bed143d2b1234854dd20bfcd5a944b869a4e09564e7 + languageName: node + linkType: hard + "@backstage/frontend-app-api@npm:^0.14.1": version: 0.14.1 resolution: "@backstage/frontend-app-api@npm:0.14.1" @@ -2875,6 +3092,21 @@ __metadata: languageName: node linkType: hard +"@backstage/integration-aws-node@npm:^0.1.13": + version: 0.1.20 + resolution: "@backstage/integration-aws-node@npm:0.1.20" + dependencies: + "@aws-sdk/client-sts": "npm:^3.350.0" + "@aws-sdk/credential-provider-node": "npm:^3.350.0" + "@aws-sdk/credential-providers": "npm:^3.350.0" + "@aws-sdk/types": "npm:^3.347.0" + "@aws-sdk/util-arn-parser": "npm:^3.310.0" + "@backstage/config": "npm:^1.3.6" + "@backstage/errors": "npm:^1.2.7" + checksum: 10/3768285e34542f266d817c916502c82e35f16cb131190f8d990692b5b7e94591e1881ded8a9b1696ee9e7f13a7c5d2cbd087f4c855d301d1891951774b52daf7 + languageName: node + linkType: hard + "@backstage/integration-react@npm:^1.2.14, @backstage/integration-react@npm:^1.2.3, @backstage/integration-react@npm:^1.2.6": version: 1.2.14 resolution: "@backstage/integration-react@npm:1.2.14" @@ -2914,6 +3146,24 @@ __metadata: languageName: node linkType: hard +"@backstage/integration@npm:^1.15.2": + version: 1.20.0 + resolution: "@backstage/integration@npm:1.20.0" + dependencies: + "@azure/identity": "npm:^4.0.0" + "@azure/storage-blob": "npm:^12.5.0" + "@backstage/config": "npm:^1.3.6" + "@backstage/errors": "npm:^1.2.7" + "@octokit/auth-app": "npm:^4.0.0" + "@octokit/rest": "npm:^19.0.3" + cross-fetch: "npm:^4.0.0" + git-url-parse: "npm:^15.0.0" + lodash: "npm:^4.17.21" + luxon: "npm:^3.0.0" + checksum: 10/c790383e5c38fde4aa80dd9e39e762b059b17af04a7175f96c62b1fcd705a59da6092238cbbcd7da1d91b44e80e6d82ed52d228b0bb3eb98936f292d75eb7aae + languageName: node + linkType: hard + "@backstage/plugin-api-docs@npm:^0.12.3": version: 0.12.11 resolution: "@backstage/plugin-api-docs@npm:0.12.11" @@ -3384,7 +3634,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-auth-node@npm:^0.5.2": +"@backstage/plugin-auth-node@npm:^0.5.2, @backstage/plugin-auth-node@npm:^0.5.3, @backstage/plugin-auth-node@npm:^0.5.4": version: 0.5.6 resolution: "@backstage/plugin-auth-node@npm:0.5.6" dependencies: @@ -3755,6 +4005,25 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-events-backend@npm:^0.5.0": + version: 0.5.11 + resolution: "@backstage/plugin-events-backend@npm:0.5.11" + dependencies: + "@backstage/backend-openapi-utils": "npm:^0.6.6" + "@backstage/backend-plugin-api": "npm:^1.7.0" + "@backstage/config": "npm:^1.3.6" + "@backstage/errors": "npm:^1.2.7" + "@backstage/plugin-events-node": "npm:^0.4.19" + "@backstage/types": "npm:^1.2.2" + "@types/express": "npm:^4.17.6" + content-type: "npm:^1.0.5" + express: "npm:^4.22.0" + express-promise-router: "npm:^4.1.0" + knex: "npm:^3.0.0" + checksum: 10/1db5efaec0308a18b727162c38506ef3905b210f6730f2c0a6b8fdf967b79b1a5d516f772f3ae4abeacc7f213b02e98335c032e9b3e6fcc43b1b03587fb677c8 + languageName: node + linkType: hard + "@backstage/plugin-events-node@npm:^0.4.10, @backstage/plugin-events-node@npm:^0.4.11, @backstage/plugin-events-node@npm:^0.4.18, @backstage/plugin-events-node@npm:^0.4.9": version: 0.4.18 resolution: "@backstage/plugin-events-node@npm:0.4.18" @@ -3772,6 +4041,23 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-events-node@npm:^0.4.19, @backstage/plugin-events-node@npm:^0.4.5": + version: 0.4.19 + resolution: "@backstage/plugin-events-node@npm:0.4.19" + dependencies: + "@backstage/backend-plugin-api": "npm:^1.7.0" + "@backstage/errors": "npm:^1.2.7" + "@backstage/types": "npm:^1.2.2" + "@types/content-type": "npm:^1.1.8" + "@types/express": "npm:^4.17.6" + content-type: "npm:^1.0.5" + cross-fetch: "npm:^4.0.0" + express: "npm:^4.22.0" + uri-template: "npm:^2.0.0" + checksum: 10/bc54ef476d30aa2de09a4efb7bd5d2bdc641d049a9d8ffca8e51710417dea8c21ec99cad83642c296eeef01055b744197f13b516baac3d03f5d935e2ac1906e1 + languageName: node + linkType: hard + "@backstage/plugin-kubernetes-common@npm:^0.9.2, @backstage/plugin-kubernetes-common@npm:^0.9.9": version: 0.9.9 resolution: "@backstage/plugin-kubernetes-common@npm:0.9.9" @@ -3854,6 +4140,28 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-permission-backend@npm:^0.5.50, @backstage/plugin-permission-backend@npm:^0.5.53": + version: 0.5.55 + resolution: "@backstage/plugin-permission-backend@npm:0.5.55" + dependencies: + "@backstage/backend-common": "npm:^0.25.0" + "@backstage/backend-plugin-api": "npm:^1.2.1" + "@backstage/config": "npm:^1.3.2" + "@backstage/errors": "npm:^1.2.7" + "@backstage/plugin-auth-node": "npm:^0.6.1" + "@backstage/plugin-permission-common": "npm:^0.8.4" + "@backstage/plugin-permission-node": "npm:^0.9.0" + "@types/express": "npm:^4.17.6" + dataloader: "npm:^2.0.0" + express: "npm:^4.17.1" + express-promise-router: "npm:^4.1.0" + lodash: "npm:^4.17.21" + yn: "npm:^4.0.0" + zod: "npm:^3.22.4" + checksum: 10/6a1bf0268cfeb2b80b603eb292e207cb1b572cc238d6dcff21e420dbab9c124c88c2896bb40afcff6395939ef9c0cfc10f5800bc05873a4a035e6358ba75abbc + languageName: node + linkType: hard + "@backstage/plugin-permission-common@npm:^0.8.1, @backstage/plugin-permission-common@npm:^0.8.4": version: 0.8.4 resolution: "@backstage/plugin-permission-common@npm:0.8.4" @@ -3902,7 +4210,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-permission-node@npm:^0.8.8": +"@backstage/plugin-permission-node@npm:^0.8.4, @backstage/plugin-permission-node@npm:^0.8.5, @backstage/plugin-permission-node@npm:^0.8.7, @backstage/plugin-permission-node@npm:^0.8.8": version: 0.8.8 resolution: "@backstage/plugin-permission-node@npm:0.8.8" dependencies: @@ -4914,7 +5222,7 @@ __metadata: languageName: node linkType: hard -"@backstage/types@npm:^1.1.1, @backstage/types@npm:^1.2.1, @backstage/types@npm:^1.2.2": +"@backstage/types@npm:^1.1.1, @backstage/types@npm:^1.2.0, @backstage/types@npm:^1.2.1, @backstage/types@npm:^1.2.2": version: 1.2.2 resolution: "@backstage/types@npm:1.2.2" checksum: 10/813129ae2f4be2765b54a16457955c8bbeb7cc6685bc2cae8b981ae7010353d9cd1110acf846f5c23cf7fbbb6bee6d56b629d5f59933247bb529f4816218c1e7 @@ -4982,6 +5290,15 @@ __metadata: languageName: node linkType: hard +"@casbin/expression-eval@npm:^5.3.0": + version: 5.3.0 + resolution: "@casbin/expression-eval@npm:5.3.0" + dependencies: + jsep: "npm:^0.3.0" + checksum: 10/df924541a02331c547eb5fdcb4291db9aa3d4b224467d64821603b036ea595bd71ad0d18aec00ce60fe29c6713b67038d7c33484ec01368be7799c8d81593b45 + languageName: node + linkType: hard + "@changesets/apply-release-plan@npm:^7.0.14": version: 7.0.14 resolution: "@changesets/apply-release-plan@npm:7.0.14" @@ -5407,7 +5724,7 @@ __metadata: languageName: node linkType: hard -"@dagrejs/graphlib@npm:2.2.4": +"@dagrejs/graphlib@npm:2.2.4, @dagrejs/graphlib@npm:^2.1.13": version: 2.2.4 resolution: "@dagrejs/graphlib@npm:2.2.4" checksum: 10/1fc5393525a3d666284ca740867d082768bc87ff61f5f181eb5c1a73c1a6e328ad23581f7415c81df2614171b8a4b4a8e6a417eefd7f9fca74a4a625ec3aa848 @@ -6828,6 +7145,15 @@ __metadata: languageName: node linkType: hard +"@janus-idp/backstage-plugin-audit-log-node@npm:^1.7.1": + version: 1.8.1 + resolution: "@janus-idp/backstage-plugin-audit-log-node@npm:1.8.1" + dependencies: + lodash: "npm:^4.17.21" + checksum: 10/7ac4510f104a8dea94bd135ee97824cb98531a4721dbfbd400883c1feb78878fca3c1042898d6a724540a689a19cc592e1023d26ae6fbb585045aac212ac70fe + languageName: node + linkType: hard + "@janus-idp/shared-react@npm:^2.13.1": version: 2.18.0 resolution: "@janus-idp/shared-react@npm:2.18.0" @@ -11793,9 +12119,13 @@ __metadata: "@backstage/cli": "npm:^0.35.2" "@backstage/errors": "npm:^1.2.7" "@backstage/plugin-catalog-node": "npm:^1.20.1" + "@backstage/plugin-permission-common": "npm:^0.8.4" + "@backstage/plugin-permission-node": "npm:^0.8.7" "@backstage/types": "npm:^1.2.2" + "@red-hat-developer-hub/backstage-plugin-dcm-common": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.12" + assert: "npm:^2.1.0" express: "npm:^4.17.1" express-promise-router: "npm:^4.1.0" supertest: "npm:^6.2.4" @@ -11803,12 +12133,13 @@ __metadata: languageName: unknown linkType: soft -"@red-hat-developer-hub/backstage-plugin-dcm-common@workspace:plugins/dcm-common": +"@red-hat-developer-hub/backstage-plugin-dcm-common@workspace:^, @red-hat-developer-hub/backstage-plugin-dcm-common@workspace:plugins/dcm-common": version: 0.0.0-use.local resolution: "@red-hat-developer-hub/backstage-plugin-dcm-common@workspace:plugins/dcm-common" dependencies: "@backstage/cli": "npm:^0.35.2" "@backstage/core-plugin-api": "npm:^1.12.2" + "@backstage/plugin-permission-common": "npm:^0.8.4" languageName: unknown linkType: soft @@ -13246,6 +13577,13 @@ __metadata: languageName: node linkType: hard +"@sqltools/formatter@npm:^1.2.5": + version: 1.2.5 + resolution: "@sqltools/formatter@npm:1.2.5" + checksum: 10/ce9335025cd033f8f1ac997d290af22d5a5cdbd5f04cbf0fa18d5388871e980a4fc67875037821799b356032f851732dee1017b2ee7de84f5c2a2b8bfd5604f5 + languageName: node + linkType: hard + "@stoplight/better-ajv-errors@npm:1.0.3": version: 1.0.3 resolution: "@stoplight/better-ajv-errors@npm:1.0.3" @@ -16190,6 +16528,13 @@ __metadata: languageName: node linkType: hard +"ansis@npm:^4.2.0": + version: 4.2.0 + resolution: "ansis@npm:4.2.0" + checksum: 10/493e15fad267bd6e3e275d6886c3b3c96a075784d9eae3e16d16383d488e94cc3deb1b357e1246f572599767360548ef9e5b7eab9b72e4ee3f7bad9ce6bc8797 + languageName: node + linkType: hard + "any-promise@npm:^1.0.0": version: 1.3.0 resolution: "any-promise@npm:1.3.0" @@ -16214,6 +16559,13 @@ __metadata: languageName: node linkType: hard +"app-root-path@npm:^3.1.0": + version: 3.1.0 + resolution: "app-root-path@npm:3.1.0" + checksum: 10/b4cdab5f7e51ec43fa04c97eca2adedf8e18d6c3dd21cd775b70457c5e71f0441c692a49dcceb426f192640b7393dcd41d85c36ef98ecb7c785a53159c912def + languageName: node + linkType: hard + "app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" @@ -16546,7 +16898,7 @@ __metadata: languageName: node linkType: hard -"assert@npm:^2.0.0": +"assert@npm:^2.0.0, assert@npm:^2.1.0": version: 2.1.0 resolution: "assert@npm:2.1.0" dependencies: @@ -16695,6 +17047,13 @@ __metadata: languageName: node linkType: hard +"await-lock@npm:^2.0.1": + version: 2.2.2 + resolution: "await-lock@npm:2.2.2" + checksum: 10/feb11f36768a8545879ed2d214b46aae484e6564ffa466af9212d5782897203770795cae01f813de04a46f66c0b8ee6bc690a0c435b04e00cad5a18ef0842e25 + languageName: node + linkType: hard + "aws-sign2@npm:~0.7.0": version: 0.7.0 resolution: "aws-sign2@npm:0.7.0" @@ -16813,6 +17172,7 @@ __metadata: version: 0.0.0-use.local resolution: "backend@workspace:packages/backend" dependencies: + "@backstage-community/plugin-rbac-backend": "npm:5.2.6" "@backstage/backend-defaults": "npm:^0.15.1" "@backstage/cli": "npm:^0.35.2" "@backstage/config": "npm:^1.3.2" @@ -16822,6 +17182,8 @@ __metadata: "@backstage/plugin-catalog-backend": "npm:^1.30.0" "@backstage/plugin-catalog-backend-module-logs": "npm:^0.1.8" "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "npm:^0.2.4" + "@backstage/plugin-events-backend": "npm:^0.5.0" + "@backstage/plugin-permission-backend": "npm:^0.5.53" "@backstage/plugin-proxy-backend": "npm:^0.5.10" "@backstage/plugin-scaffolder-backend": "npm:^1.29.0" "@backstage/plugin-search-backend": "npm:^1.8.1" @@ -17646,6 +18008,19 @@ __metadata: languageName: node linkType: hard +"casbin@npm:^5.27.0, casbin@npm:^5.27.1": + version: 5.49.0 + resolution: "casbin@npm:5.49.0" + dependencies: + "@casbin/expression-eval": "npm:^5.3.0" + await-lock: "npm:^2.0.1" + buffer: "npm:^6.0.3" + csv-parse: "npm:^5.5.6" + minimatch: "npm:^10.2.1" + checksum: 10/a608363d63f35a2284161951ef41503651107c3f8877425b412ee7336d127801ad38ea2de204f30c6f58c0ab1a192a2661410a491e2708281bd564de550e8018 + languageName: node + linkType: hard + "caseless@npm:~0.12.0": version: 0.12.0 resolution: "caseless@npm:0.12.0" @@ -18990,6 +19365,13 @@ __metadata: languageName: node linkType: hard +"csv-parse@npm:^5.5.5, csv-parse@npm:^5.5.6": + version: 5.6.0 + resolution: "csv-parse@npm:5.6.0" + checksum: 10/4c82e11f50ae0ccbac2aed716ef2502d0468bf96552083561db789fc0258ee4bb0a30106fcfb2684f153cb4042f0413e0eac3645d5466874803b7ccdeba67ac8 + languageName: node + linkType: hard + "ctrlc-windows@npm:^2.1.0": version: 2.2.0 resolution: "ctrlc-windows@npm:2.2.0" @@ -19208,6 +19590,13 @@ __metadata: languageName: node linkType: hard +"dayjs@npm:^1.11.19": + version: 1.11.19 + resolution: "dayjs@npm:1.11.19" + checksum: 10/185b820d68492b83a3ce2b8ddc7543034edc1dfd1423183f6ae4707b29929a3cc56503a81826309279f9084680c15966b99456e74cf41f7d1f6a2f98f9c7196f + languageName: node + linkType: hard + "debounce-promise@npm:^3.1.2": version: 3.1.2 resolution: "debounce-promise@npm:3.1.2" @@ -19289,6 +19678,18 @@ __metadata: languageName: node linkType: hard +"dedent@npm:^1.7.0": + version: 1.7.1 + resolution: "dedent@npm:1.7.1" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10/78785ef592e37e0b1ca7a7a5964c8f3dee1abdff46c5bb49864168579c122328f6bb55c769bc7e005046a7381c3372d3859f0f78ab083950fa146e1c24873f4f + languageName: node + linkType: hard + "deep-equal@npm:^2.0.5": version: 2.2.3 resolution: "deep-equal@npm:2.2.3" @@ -19802,6 +20203,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.6.1": + version: 16.6.1 + resolution: "dotenv@npm:16.6.1" + checksum: 10/1d1897144344447ffe62aa1a6d664f4cd2e0784e0aff787eeeec1940ded32f8e4b5b506d665134fc87157baa086fce07ec6383970a2b6d2e7985beaed6a4cc14 + languageName: node + linkType: hard + "drange@npm:^1.0.2": version: 1.1.1 resolution: "drange@npm:1.1.1" @@ -20900,7 +21308,7 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.14.0, express@npm:^4.17.1, express@npm:^4.22.0, express@npm:^4.22.1": +"express@npm:^4.14.0, express@npm:^4.17.1, express@npm:^4.18.2, express@npm:^4.22.0, express@npm:^4.22.1": version: 4.22.1 resolution: "express@npm:4.22.1" dependencies: @@ -22015,7 +22423,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.4.1": +"glob@npm:^10.0.0, glob@npm:^10.4.1, glob@npm:^10.5.0": version: 10.5.0 resolution: "glob@npm:10.5.0" dependencies: @@ -24379,6 +24787,13 @@ __metadata: languageName: node linkType: hard +"jsep@npm:^0.3.0": + version: 0.3.5 + resolution: "jsep@npm:0.3.5" + checksum: 10/cfdb0cf6553dc2febc85e2741667f81fdd1d7b062207c8fbf03dea399f29c7c3cd448f3147e96d1b2082a399e8a419a5462d5c79569efb18dba0eb3596755e50 + languageName: node + linkType: hard + "jsep@npm:^1.2.0, jsep@npm:^1.3.6, jsep@npm:^1.4.0": version: 1.4.0 resolution: "jsep@npm:1.4.0" @@ -30725,13 +31140,20 @@ __metadata: languageName: node linkType: hard -"reflect-metadata@npm:0.2.2": +"reflect-metadata@npm:0.2.2, reflect-metadata@npm:^0.2.2": version: 0.2.2 resolution: "reflect-metadata@npm:0.2.2" checksum: 10/1c93f9ac790fea1c852fde80c91b2760420069f4862f28e6fae0c00c6937a56508716b0ed2419ab02869dd488d123c4ab92d062ae84e8739ea7417fae10c4745 languageName: node linkType: hard +"reflect-metadata@npm:^0.1.13": + version: 0.1.14 + resolution: "reflect-metadata@npm:0.1.14" + checksum: 10/fcab9c17ec3b9fea0e2f748c2129aceb57c24af6d8d13842b8a77c8c79dde727d7456ce293e76e8d7b267d1dbf93eea4c5b3c9101299a789a075824f2e40f1ee + languageName: node + linkType: hard + "reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": version: 1.0.10 resolution: "reflect.getprototypeof@npm:1.0.10" @@ -32198,6 +32620,13 @@ __metadata: languageName: node linkType: hard +"sql-highlight@npm:^6.1.0": + version: 6.1.0 + resolution: "sql-highlight@npm:6.1.0" + checksum: 10/6cd92e7ca3046563f3daf2086adc4c2e1ce43784e59827a12bb9e569bf915eace1d800713f4d2798fc7d475f64852bf08001dca8dd409e9895ba5e0e170b94ff + languageName: node + linkType: hard + "ssh-remote-port-forward@npm:^1.0.4": version: 1.0.4 resolution: "ssh-remote-port-forward@npm:1.0.4" @@ -33843,6 +34272,94 @@ __metadata: languageName: node linkType: hard +"typeorm-adapter@npm:^1.6.1": + version: 1.9.0 + resolution: "typeorm-adapter@npm:1.9.0" + dependencies: + casbin: "npm:^5.27.0" + reflect-metadata: "npm:^0.1.13" + typeorm: "npm:^0.3.17" + checksum: 10/6c96409508ddd792ca71bd561f54c5f75249093bb301ee9eb451fa2e6d21dbb49aff396486538e7b1e06663d99deb9070f2ecd2170852ab69a2fd3ec9fd68ab8 + languageName: node + linkType: hard + +"typeorm@npm:^0.3.17": + version: 0.3.28 + resolution: "typeorm@npm:0.3.28" + dependencies: + "@sqltools/formatter": "npm:^1.2.5" + ansis: "npm:^4.2.0" + app-root-path: "npm:^3.1.0" + buffer: "npm:^6.0.3" + dayjs: "npm:^1.11.19" + debug: "npm:^4.4.3" + dedent: "npm:^1.7.0" + dotenv: "npm:^16.6.1" + glob: "npm:^10.5.0" + reflect-metadata: "npm:^0.2.2" + sha.js: "npm:^2.4.12" + sql-highlight: "npm:^6.1.0" + tslib: "npm:^2.8.1" + uuid: "npm:^11.1.0" + yargs: "npm:^17.7.2" + peerDependencies: + "@google-cloud/spanner": ^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + "@sap/hana-client": ^2.14.22 + better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 + ioredis: ^5.0.4 + mongodb: ^5.8.0 || ^6.0.0 + mssql: ^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 || ^5.0.14 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 + peerDependenciesMeta: + "@google-cloud/spanner": + optional: true + "@sap/hana-client": + optional: true + better-sqlite3: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + bin: + typeorm: cli.js + typeorm-ts-node-commonjs: cli-ts-node-commonjs.js + typeorm-ts-node-esm: cli-ts-node-esm.js + checksum: 10/4eb217d65414291fb226267d903d123a16a9eb090b4e8f8da2dfe2f64680265823bea3712d363d31fe96c6dc2cef00edd545f42e63ec65b8a1899a1b455f757b + languageName: node + linkType: hard + "types-ramda@npm:^0.30.1": version: 0.30.1 resolution: "types-ramda@npm:0.30.1" @@ -34476,7 +34993,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^11.0.0, uuid@npm:^11.0.2": +"uuid@npm:^11.0.0, uuid@npm:^11.0.2, uuid@npm:^11.1.0": version: 11.1.0 resolution: "uuid@npm:11.1.0" bin: From d2450652accb64f4e4da3bef897be10f459feb90 Mon Sep 17 00:00:00 2001 From: Ashraf Masarwa Date: Tue, 3 Mar 2026 16:10:41 +0200 Subject: [PATCH 2/4] Fix CI --- workspaces/dcm/packages/app/src/App.tsx | 1 - .../plugins/dcm-backend/src/router.test.ts | 56 +------ .../plugins/dcm-backend/src/routes/access.ts | 2 +- workspaces/dcm/yarn.lock | 153 +----------------- 4 files changed, 17 insertions(+), 195 deletions(-) diff --git a/workspaces/dcm/packages/app/src/App.tsx b/workspaces/dcm/packages/app/src/App.tsx index 13e01253ae..423c3cae3d 100644 --- a/workspaces/dcm/packages/app/src/App.tsx +++ b/workspaces/dcm/packages/app/src/App.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import React from 'react'; import { Navigate, Route } from 'react-router-dom'; import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; import { diff --git a/workspaces/dcm/plugins/dcm-backend/src/router.test.ts b/workspaces/dcm/plugins/dcm-backend/src/router.test.ts index 49b4ce228b..863feb2804 100644 --- a/workspaces/dcm/plugins/dcm-backend/src/router.test.ts +++ b/workspaces/dcm/plugins/dcm-backend/src/router.test.ts @@ -14,70 +14,30 @@ * limitations under the License. */ /* eslint-disable @backstage/no-undeclared-imports -- deps in dcm-backend package.json */ -import { - mockCredentials, - mockErrorHandler, - mockServices, -} from '@backstage/backend-test-utils'; +import { mockServices } from '@backstage/backend-test-utils'; import express from 'express'; import request from 'supertest'; import { createRouter } from './router'; -import { todoListServiceRef } from './services/TodoListService'; -const mockTodoItem = { - title: 'Do the thing', - id: '123', - createdBy: mockCredentials.user().principal.userEntityRef, - createdAt: new Date().toISOString(), -}; - -// TEMPLATE NOTE: -// Testing the router directly allows you to write a unit test that mocks the provided options. describe('createRouter', () => { let app: express.Express; - let todoList: jest.Mocked; beforeEach(async () => { - todoList = { - createTodo: jest.fn(), - listTodos: jest.fn(), - getTodo: jest.fn(), - }; const router = await createRouter({ + logger: mockServices.rootLogger(), httpAuth: mockServices.httpAuth(), - todoList, + permissions: mockServices.permissions.mock(), + cache: mockServices.cache.mock(), }); app = express(); app.use(router); - app.use(mockErrorHandler()); }); - it('should create a TODO', async () => { - todoList.createTodo.mockResolvedValue(mockTodoItem); - - const response = await request(app).post('/todos').send({ - title: 'Do the thing', - }); - - expect(response.status).toBe(201); - expect(response.body).toEqual(mockTodoItem); - }); - - it('should not allow unauthenticated requests to create a TODO', async () => { - todoList.createTodo.mockResolvedValue(mockTodoItem); - - // TEMPLATE NOTE: - // The HttpAuth mock service considers all requests to be authenticated as a - // mock user by default. In order to test other cases we need to explicitly - // pass an authorization header with mock credentials. - const response = await request(app) - .post('/todos') - .set('Authorization', mockCredentials.none.header()) - .send({ - title: 'Do the thing', - }); + it('should return ok for GET /health', async () => { + const response = await request(app).get('/health'); - expect(response.status).toBe(401); + expect(response.status).toBe(200); + expect(response.body).toEqual({ status: 'ok' }); }); }); diff --git a/workspaces/dcm/plugins/dcm-backend/src/routes/access.ts b/workspaces/dcm/plugins/dcm-backend/src/routes/access.ts index 44976a8244..cdf6abaa0f 100644 --- a/workspaces/dcm/plugins/dcm-backend/src/routes/access.ts +++ b/workspaces/dcm/plugins/dcm-backend/src/routes/access.ts @@ -40,7 +40,7 @@ export const getAccess = const decision = await authorize(req, options); - logger.info('DCM access decision:', decision); + logger.info(`DCM access decision: ${decision}`); response.json({ decision, diff --git a/workspaces/dcm/yarn.lock b/workspaces/dcm/yarn.lock index 5350143d6a..efeccfc7d3 100644 --- a/workspaces/dcm/yarn.lock +++ b/workspaces/dcm/yarn.lock @@ -2113,31 +2113,7 @@ __metadata: languageName: node linkType: hard -"@backstage/backend-openapi-utils@npm:^0.6.5": - version: 0.6.5 - resolution: "@backstage/backend-openapi-utils@npm:0.6.5" - dependencies: - "@apidevtools/swagger-parser": "npm:^10.1.0" - "@backstage/backend-plugin-api": "npm:^1.6.1" - "@backstage/errors": "npm:^1.2.7" - "@backstage/types": "npm:^1.2.2" - "@types/express": "npm:^4.17.6" - "@types/express-serve-static-core": "npm:^4.17.5" - ajv: "npm:^8.16.0" - express: "npm:^4.22.0" - express-openapi-validator: "npm:^5.5.8" - express-promise-router: "npm:^4.1.0" - get-port: "npm:^5.1.1" - json-schema-to-ts: "npm:^3.0.0" - lodash: "npm:^4.17.21" - mockttp: "npm:^3.13.0" - openapi-merge: "npm:^1.3.2" - openapi3-ts: "npm:^3.1.2" - checksum: 10/e65f216905a50698deedda54e1cc38e89e8f63b136bce42b3d59d9e9a1055837c6534bd222d296c5b1dd6b6741cd5ff934563c80ddbece052b0936ad197b2b35 - languageName: node - linkType: hard - -"@backstage/backend-openapi-utils@npm:^0.6.6": +"@backstage/backend-openapi-utils@npm:^0.6.5, @backstage/backend-openapi-utils@npm:^0.6.6": version: 0.6.6 resolution: "@backstage/backend-openapi-utils@npm:0.6.6" dependencies: @@ -2223,19 +2199,7 @@ __metadata: languageName: node linkType: hard -"@backstage/catalog-client@npm:^1.10.0, @backstage/catalog-client@npm:^1.11.0, @backstage/catalog-client@npm:^1.12.1, @backstage/catalog-client@npm:^1.9.1": - version: 1.12.1 - resolution: "@backstage/catalog-client@npm:1.12.1" - dependencies: - "@backstage/catalog-model": "npm:^1.7.6" - "@backstage/errors": "npm:^1.2.7" - cross-fetch: "npm:^4.0.0" - uri-template: "npm:^2.0.0" - checksum: 10/f8963a68150e3aa7001128c9ee73d7a022260b69aa08902c8f9a05861797609bebdb7a98ded1844c3e2bd96298e0673f21a0d0125d8cbbd85110cbaa52efee90 - languageName: node - linkType: hard - -"@backstage/catalog-client@npm:^1.7.1": +"@backstage/catalog-client@npm:^1.10.0, @backstage/catalog-client@npm:^1.11.0, @backstage/catalog-client@npm:^1.12.1, @backstage/catalog-client@npm:^1.7.1, @backstage/catalog-client@npm:^1.9.1": version: 1.13.0 resolution: "@backstage/catalog-client@npm:1.13.0" dependencies: @@ -2261,19 +2225,7 @@ __metadata: languageName: node linkType: hard -"@backstage/cli-common@npm:^0.1.14, @backstage/cli-common@npm:^0.1.16, @backstage/cli-common@npm:^0.1.17": - version: 0.1.17 - resolution: "@backstage/cli-common@npm:0.1.17" - dependencies: - "@backstage/errors": "npm:^1.2.7" - cross-spawn: "npm:^7.0.3" - global-agent: "npm:^3.0.0" - undici: "npm:^7.2.3" - checksum: 10/e5b1d87548c739012554bb68f5bcd84239f8692bbce3e79a46299cd70308ab144615be9d3ad96c78d4796eb52faa8d50f09226d3d61411c05251d4d0d24b7e7f - languageName: node - linkType: hard - -"@backstage/cli-common@npm:^0.1.15, @backstage/cli-common@npm:^0.1.18": +"@backstage/cli-common@npm:^0.1.14, @backstage/cli-common@npm:^0.1.15, @backstage/cli-common@npm:^0.1.17, @backstage/cli-common@npm:^0.1.18": version: 0.1.18 resolution: "@backstage/cli-common@npm:0.1.18" dependencies: @@ -2285,7 +2237,7 @@ __metadata: languageName: node linkType: hard -"@backstage/cli-node@npm:^0.2.10": +"@backstage/cli-node@npm:^0.2.10, @backstage/cli-node@npm:^0.2.13, @backstage/cli-node@npm:^0.2.17": version: 0.2.18 resolution: "@backstage/cli-node@npm:0.2.18" dependencies: @@ -2301,22 +2253,6 @@ __metadata: languageName: node linkType: hard -"@backstage/cli-node@npm:^0.2.13, @backstage/cli-node@npm:^0.2.17": - version: 0.2.17 - resolution: "@backstage/cli-node@npm:0.2.17" - dependencies: - "@backstage/cli-common": "npm:^0.1.17" - "@backstage/errors": "npm:^1.2.7" - "@backstage/types": "npm:^1.2.2" - "@manypkg/get-packages": "npm:^1.1.3" - "@yarnpkg/parsers": "npm:^3.0.0" - fs-extra: "npm:^11.2.0" - semver: "npm:^7.5.3" - zod: "npm:^3.25.76" - checksum: 10/2bcce77ad5c5c34cfd1380a7fa5b3f01b0e98f4a42b0fc2927f3c41f553b78766d908f656584fc1870107913f92f7c037de0323f13b1132ef000336a560e6b84 - languageName: node - linkType: hard - "@backstage/cli@npm:^0.35.2": version: 0.35.3 resolution: "@backstage/cli@npm:0.35.3" @@ -2463,30 +2399,7 @@ __metadata: languageName: node linkType: hard -"@backstage/config-loader@npm:^1.10.0, @backstage/config-loader@npm:^1.10.1, @backstage/config-loader@npm:^1.10.7, @backstage/config-loader@npm:^1.9.1, @backstage/config-loader@npm:^1.9.6": - version: 1.10.7 - resolution: "@backstage/config-loader@npm:1.10.7" - dependencies: - "@backstage/cli-common": "npm:^0.1.16" - "@backstage/config": "npm:^1.3.6" - "@backstage/errors": "npm:^1.2.7" - "@backstage/types": "npm:^1.2.2" - "@types/json-schema": "npm:^7.0.6" - ajv: "npm:^8.10.0" - chokidar: "npm:^3.5.2" - fs-extra: "npm:^11.2.0" - json-schema: "npm:^0.4.0" - json-schema-merge-allof: "npm:^0.8.1" - json-schema-traverse: "npm:^1.0.0" - lodash: "npm:^4.17.21" - minimist: "npm:^1.2.5" - typescript-json-schema: "npm:^0.67.0" - yaml: "npm:^2.0.0" - checksum: 10/36b73687663a6d380db884955c66f8f8616cf6fbf61469a8b2bc7a6de59f41754aa121cc836c143248354eea672a5cce781eee9c724fe1949033320cbbca99fb - languageName: node - linkType: hard - -"@backstage/config-loader@npm:^1.9.2": +"@backstage/config-loader@npm:^1.10.0, @backstage/config-loader@npm:^1.10.1, @backstage/config-loader@npm:^1.10.7, @backstage/config-loader@npm:^1.9.1, @backstage/config-loader@npm:^1.9.2, @backstage/config-loader@npm:^1.9.6": version: 1.10.8 resolution: "@backstage/config-loader@npm:1.10.8" dependencies: @@ -3077,22 +2990,7 @@ __metadata: languageName: node linkType: hard -"@backstage/integration-aws-node@npm:^0.1.12, @backstage/integration-aws-node@npm:^0.1.15, @backstage/integration-aws-node@npm:^0.1.16, @backstage/integration-aws-node@npm:^0.1.19": - version: 0.1.19 - resolution: "@backstage/integration-aws-node@npm:0.1.19" - dependencies: - "@aws-sdk/client-sts": "npm:^3.350.0" - "@aws-sdk/credential-provider-node": "npm:^3.350.0" - "@aws-sdk/credential-providers": "npm:^3.350.0" - "@aws-sdk/types": "npm:^3.347.0" - "@aws-sdk/util-arn-parser": "npm:^3.310.0" - "@backstage/config": "npm:^1.3.6" - "@backstage/errors": "npm:^1.2.7" - checksum: 10/b6a55ef787d88dd0ee41b5f4184508789cb3fdda54a5aaebba0db6b48604718666cc61969b6b41a3ddb6fedd38f7acef3884b0fc37471b8a3bd646925700f80d - languageName: node - linkType: hard - -"@backstage/integration-aws-node@npm:^0.1.13": +"@backstage/integration-aws-node@npm:^0.1.12, @backstage/integration-aws-node@npm:^0.1.13, @backstage/integration-aws-node@npm:^0.1.15, @backstage/integration-aws-node@npm:^0.1.16, @backstage/integration-aws-node@npm:^0.1.19": version: 0.1.20 resolution: "@backstage/integration-aws-node@npm:0.1.20" dependencies: @@ -3128,25 +3026,7 @@ __metadata: languageName: node linkType: hard -"@backstage/integration@npm:^1.15.0, @backstage/integration@npm:^1.16.1, @backstage/integration@npm:^1.16.2, @backstage/integration@npm:^1.16.3, @backstage/integration@npm:^1.17.0, @backstage/integration@npm:^1.18.1, @backstage/integration@npm:^1.18.2, @backstage/integration@npm:^1.19.2": - version: 1.19.2 - resolution: "@backstage/integration@npm:1.19.2" - dependencies: - "@azure/identity": "npm:^4.0.0" - "@azure/storage-blob": "npm:^12.5.0" - "@backstage/config": "npm:^1.3.6" - "@backstage/errors": "npm:^1.2.7" - "@octokit/auth-app": "npm:^4.0.0" - "@octokit/rest": "npm:^19.0.3" - cross-fetch: "npm:^4.0.0" - git-url-parse: "npm:^15.0.0" - lodash: "npm:^4.17.21" - luxon: "npm:^3.0.0" - checksum: 10/20114acea62636de02c52bdf2e7e6a5cc426bbdb23f343932bc60baed831fc299b3b53ce6e45687027cf5ca7265c67d3e9dee23dd03791948c0729538513c232 - languageName: node - linkType: hard - -"@backstage/integration@npm:^1.15.2": +"@backstage/integration@npm:^1.15.0, @backstage/integration@npm:^1.15.2, @backstage/integration@npm:^1.16.1, @backstage/integration@npm:^1.16.2, @backstage/integration@npm:^1.16.3, @backstage/integration@npm:^1.17.0, @backstage/integration@npm:^1.18.1, @backstage/integration@npm:^1.18.2, @backstage/integration@npm:^1.19.2": version: 1.20.0 resolution: "@backstage/integration@npm:1.20.0" dependencies: @@ -4024,24 +3904,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-events-node@npm:^0.4.10, @backstage/plugin-events-node@npm:^0.4.11, @backstage/plugin-events-node@npm:^0.4.18, @backstage/plugin-events-node@npm:^0.4.9": - version: 0.4.18 - resolution: "@backstage/plugin-events-node@npm:0.4.18" - dependencies: - "@backstage/backend-plugin-api": "npm:^1.6.0" - "@backstage/errors": "npm:^1.2.7" - "@backstage/types": "npm:^1.2.2" - "@types/content-type": "npm:^1.1.8" - "@types/express": "npm:^4.17.6" - content-type: "npm:^1.0.5" - cross-fetch: "npm:^4.0.0" - express: "npm:^4.22.0" - uri-template: "npm:^2.0.0" - checksum: 10/214f62c4d49fba4303bd391874cc784d30a4e198bd193a121445cd937a33f287cbdc477239d28624310c897d3a30ed405ab478089171d43eb71bd1181b202058 - languageName: node - linkType: hard - -"@backstage/plugin-events-node@npm:^0.4.19, @backstage/plugin-events-node@npm:^0.4.5": +"@backstage/plugin-events-node@npm:^0.4.10, @backstage/plugin-events-node@npm:^0.4.11, @backstage/plugin-events-node@npm:^0.4.18, @backstage/plugin-events-node@npm:^0.4.19, @backstage/plugin-events-node@npm:^0.4.5, @backstage/plugin-events-node@npm:^0.4.9": version: 0.4.19 resolution: "@backstage/plugin-events-node@npm:0.4.19" dependencies: From 1af8f9f930306e54d975d15773f986466096b185 Mon Sep 17 00:00:00 2001 From: Ashraf Masarwa Date: Tue, 3 Mar 2026 16:46:55 +0200 Subject: [PATCH 3/4] Fix CI --- workspaces/dcm/package.json | 2 +- workspaces/dcm/plugins/dcm-common/report.api.md | 8 +++++++- workspaces/dcm/plugins/dcm-common/src/index.ts | 6 ++++-- workspaces/dcm/plugins/dcm-common/src/permissions.ts | 12 ++++++++++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/workspaces/dcm/package.json b/workspaces/dcm/package.json index b77b4e105d..eb67a44e53 100644 --- a/workspaces/dcm/package.json +++ b/workspaces/dcm/package.json @@ -16,7 +16,7 @@ "tsc:full": "tsc --skipLibCheck true --incremental false", "build:all": "backstage-cli repo build --all", "build:api-reports": "yarn build:api-reports:only --tsc", - "build:api-reports:only": "backstage-repo-tools api-reports -o ae-wrong-input-file-type --validate-release-tags", + "build:api-reports:only": "backstage-repo-tools api-reports -o ae-wrong-input-file-type,ae-missing-release-tag --validate-release-tags", "build:knip-reports": "backstage-repo-tools knip-reports", "clean": "backstage-cli repo clean", "test": "backstage-cli repo test", diff --git a/workspaces/dcm/plugins/dcm-common/report.api.md b/workspaces/dcm/plugins/dcm-common/report.api.md index a4f767e7cf..d7e453b68f 100644 --- a/workspaces/dcm/plugins/dcm-common/report.api.md +++ b/workspaces/dcm/plugins/dcm-common/report.api.md @@ -3,8 +3,14 @@ > 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'; + // @public export const DCM_COMMON_PLUGIN_ID: 'dcm'; -// (No @packageDocumentation comment for this package) +// @public +export const dcmPluginPermissions: BasicPermission[]; + +// @public +export const dcmPluginReadPermission: BasicPermission; ``` diff --git a/workspaces/dcm/plugins/dcm-common/src/index.ts b/workspaces/dcm/plugins/dcm-common/src/index.ts index 561858466d..d3e78f7c41 100644 --- a/workspaces/dcm/plugins/dcm-common/src/index.ts +++ b/workspaces/dcm/plugins/dcm-common/src/index.ts @@ -15,11 +15,13 @@ */ /** - * Common types and utilities for the dcm plugin. + * Common types and utilities for the DCM plugin. * Add shared code between frontend and backend plugins here. * - * @public + * @packageDocumentation */ + +/** Plugin ID for the DCM plugin. @public */ export const DCM_COMMON_PLUGIN_ID = 'dcm' as const; export { dcmPluginReadPermission, dcmPluginPermissions } from './permissions'; diff --git a/workspaces/dcm/plugins/dcm-common/src/permissions.ts b/workspaces/dcm/plugins/dcm-common/src/permissions.ts index a179bd268f..3e35f2b41a 100644 --- a/workspaces/dcm/plugins/dcm-common/src/permissions.ts +++ b/workspaces/dcm/plugins/dcm-common/src/permissions.ts @@ -16,11 +16,19 @@ import { createPermission } from '@backstage/plugin-permission-common'; -/** @public */ +/** + * Permission for reading the DCM plugin. + * + * @public + */ export const dcmPluginReadPermission = createPermission({ name: 'dcm.plugin', attributes: { action: 'read' }, }); -/** @public */ +/** + * List of all DCM plugin permissions. + * + * @public + */ export const dcmPluginPermissions = [dcmPluginReadPermission]; From 1153ad275dca12c0303ea0b973e9c5c81cdb2dfa Mon Sep 17 00:00:00 2001 From: Ashraf Masarwa Date: Tue, 3 Mar 2026 16:57:41 +0200 Subject: [PATCH 4/4] Fix CI --- .../plugins/dcm-backend/src/plugin.test.ts | 123 +----------------- 1 file changed, 5 insertions(+), 118 deletions(-) diff --git a/workspaces/dcm/plugins/dcm-backend/src/plugin.test.ts b/workspaces/dcm/plugins/dcm-backend/src/plugin.test.ts index 37ba0e6383..b386c5713d 100644 --- a/workspaces/dcm/plugins/dcm-backend/src/plugin.test.ts +++ b/workspaces/dcm/plugins/dcm-backend/src/plugin.test.ts @@ -14,132 +14,19 @@ * limitations under the License. */ /* eslint-disable @backstage/no-undeclared-imports -- deps in dcm-backend package.json */ -import { - mockCredentials, - startTestBackend, -} from '@backstage/backend-test-utils'; -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { todoListServiceRef } from './services/TodoListService'; +import { startTestBackend } from '@backstage/backend-test-utils'; import { dcmPlugin } from './plugin'; import request from 'supertest'; -import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; -import { - ConflictError, - AuthenticationError, - NotAllowedError, -} from '@backstage/errors'; -// TEMPLATE NOTE: -// Plugin tests are integration tests for your plugin, ensuring that all pieces -// work together end-to-end. You can still mock injected backend services -// however, just like anyone who installs your plugin might replace the -// services with their own implementations. describe('plugin', () => { - it('should create and read TODO items', async () => { + it('should serve health endpoint', async () => { const { server } = await startTestBackend({ features: [dcmPlugin], }); - await request(server).get('/api/dcm/todos').expect(200, { - items: [], - }); - - const createRes = await request(server) - .post('/api/dcm/todos') - .send({ title: 'My Todo' }); - - expect(createRes.status).toBe(201); - expect(createRes.body).toEqual({ - id: expect.any(String), - title: 'My Todo', - createdBy: mockCredentials.user().principal.userEntityRef, - createdAt: expect.any(String), - }); - - const createdTodoItem = createRes.body; - - await request(server) - .get('/api/dcm/todos') - .expect(200, { - items: [createdTodoItem], - }); - - await request(server) - .get(`/api/dcm/todos/${createdTodoItem.id}`) - .expect(200, createdTodoItem); - }); - - it('should create TODO item with catalog information', async () => { - const { server } = await startTestBackend({ - features: [ - dcmPlugin, - catalogServiceMock.factory({ - entities: [ - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - name: 'my-component', - namespace: 'default', - title: 'My Component', - }, - spec: { - type: 'service', - owner: 'me', - }, - }, - ], - }), - ], - }); - - const createRes = await request(server) - .post('/api/dcm/todos') - .send({ title: 'My Todo', entityRef: 'component:default/my-component' }); + const res = await request(server).get('/api/dcm/health'); - expect(createRes.status).toBe(201); - expect(createRes.body).toEqual({ - id: expect.any(String), - title: '[My Component] My Todo', - createdBy: mockCredentials.user().principal.userEntityRef, - createdAt: expect.any(String), - }); - }); - - it('should forward errors from the TodoListService', async () => { - const { server } = await startTestBackend({ - features: [ - dcmPlugin, - createServiceFactory({ - service: todoListServiceRef, - deps: {}, - factory: () => ({ - createTodo: jest.fn().mockRejectedValue(new ConflictError()), - listTodos: jest.fn().mockRejectedValue(new AuthenticationError()), - getTodo: jest.fn().mockRejectedValue(new NotAllowedError()), - }), - }), - ], - }); - - const createRes = await request(server) - .post('/api/dcm/todos') - .send({ title: 'My Todo', entityRef: 'component:default/my-component' }); - expect(createRes.status).toBe(409); - expect(createRes.body).toMatchObject({ - error: { name: 'ConflictError' }, - }); - - const listRes = await request(server).get('/api/dcm/todos'); - expect(listRes.status).toBe(401); - expect(listRes.body).toMatchObject({ - error: { name: 'AuthenticationError' }, - }); - - const getRes = await request(server).get('/api/dcm/todos/123'); - expect(getRes.status).toBe(403); - expect(getRes.body).toMatchObject({ - error: { name: 'NotAllowedError' }, - }); + expect(res.status).toBe(200); + expect(res.body).toEqual({ status: 'ok' }); }); });