diff --git a/plugins/azure-sites-backend/api-report.md b/plugins/azure-sites-backend/api-report.md index b8d8f23c13..04cd22596a 100644 --- a/plugins/azure-sites-backend/api-report.md +++ b/plugins/azure-sites-backend/api-report.md @@ -6,6 +6,7 @@ import { AzureSiteListRequest } from '@backstage/plugin-azure-sites-common'; import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common'; import { AzureSiteStartStopRequest } from '@backstage/plugin-azure-sites-common'; +import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import express from 'express'; import { Logger } from 'winston'; @@ -57,6 +58,8 @@ export interface RouterOptions { // (undocumented) azureSitesApi: AzureSitesApi; // (undocumented) + catalogApi: CatalogApi; + // (undocumented) logger: Logger; // (undocumented) permissions: PermissionEvaluator; diff --git a/plugins/azure-sites-backend/package.json b/plugins/azure-sites-backend/package.json index 4426798ca2..2599bfc360 100644 --- a/plugins/azure-sites-backend/package.json +++ b/plugins/azure-sites-backend/package.json @@ -36,6 +36,7 @@ "@azure/arm-resourcegraph": "^4.2.1", "@azure/identity": "^4.0.0", "@backstage/backend-common": "workspace:^", + "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/azure-sites-backend/src/service/router.ts b/plugins/azure-sites-backend/src/service/router.ts index c7661239e5..fcab14a97f 100644 --- a/plugins/azure-sites-backend/src/service/router.ts +++ b/plugins/azure-sites-backend/src/service/router.ts @@ -30,6 +30,7 @@ import { azureSitesActionPermission, AZURE_WEB_SITE_NAME_ANNOTATION, } from '@backstage/plugin-azure-sites-common'; +import { CatalogApi } from '@backstage/catalog-client'; import { AzureSitesApi } from '../api'; @@ -37,6 +38,7 @@ import { AzureSitesApi } from '../api'; export interface RouterOptions { logger: Logger; azureSitesApi: AzureSitesApi; + catalogApi: CatalogApi; permissions: PermissionEvaluator; } @@ -44,7 +46,7 @@ export interface RouterOptions { export async function createRouter( options: RouterOptions, ): Promise { - const { logger, azureSitesApi, permissions } = options; + const { logger, azureSitesApi, permissions, catalogApi } = options; const router = Router(); router.use(express.json()); @@ -68,37 +70,45 @@ export async function createRouter( const token = getBearerTokenFromAuthorizationHeader( request.header('authorization'), ); - const entity = request.body.entity; - const resourceRef = stringifyEntityRef(entity); + const entityRef = request.body.entityRef; + const entity = await catalogApi.getEntityByRef(entityRef); - const annotationName = - entity.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION]; - if (await azureSitesApi.validateSite(annotationName, name)) { + if (entity) { + const annotationName = + entity.metadata?.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION]; + const resourceRef = stringifyEntityRef(entity); + if ( + annotationName && + (await azureSitesApi.validateSite(annotationName, name)) + ) { + throw new NotAllowedError(); + } + + const decision = ( + await permissions.authorize( + [{ permission: azureSitesActionPermission, resourceRef }], + { + token, + }, + ) + )[0]; + if (decision.result === AuthorizeResult.DENY) { + throw new NotAllowedError('Unauthorized'); + } + + logger.info( + `entity ${entity.metadata.name} - azure site "${name}" is starting...`, + ); + response.json( + await azureSitesApi.start({ + subscription, + resourceGroup, + name, + }), + ); + } else { throw new NotAllowedError(); } - - const decision = ( - await permissions.authorize( - [{ permission: azureSitesActionPermission, resourceRef }], - { - token, - }, - ) - )[0]; - if (decision.result === AuthorizeResult.DENY) { - throw new NotAllowedError('Unauthorized'); - } - - logger.info( - `entity ${entity.metadata.name} - azure site "${name}" is starting...`, - ); - response.json( - await azureSitesApi.start({ - subscription, - resourceGroup, - name, - }), - ); }, ); router.post( @@ -108,37 +118,46 @@ export async function createRouter( const token = getBearerTokenFromAuthorizationHeader( request.header('authorization'), ); - const entity = request.body.entity; - const resourceRef = stringifyEntityRef(entity); - const annotationName = - entity.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION]; - if (await azureSitesApi.validateSite(annotationName, name)) { + const entityRef = request.body.entityRef; + const entity = await catalogApi.getEntityByRef(entityRef); + + if (entity) { + const annotationName = + entity.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION]; + const resourceRef = stringifyEntityRef(entity); + if ( + annotationName && + (await azureSitesApi.validateSite(annotationName, name)) + ) { + throw new NotAllowedError(); + } + + const decision = ( + await permissions.authorize( + [{ permission: azureSitesActionPermission, resourceRef }], + { + token, + }, + ) + )[0]; + if (decision.result === AuthorizeResult.DENY) { + throw new NotAllowedError('Unauthorized'); + } + + logger.info( + `entity ${entity.metadata.name} - azure site "${name}" is stopping...`, + ); + response.json( + await azureSitesApi.stop({ + subscription, + resourceGroup, + name, + }), + ); + } else { throw new NotAllowedError(); } - - const decision = ( - await permissions.authorize( - [{ permission: azureSitesActionPermission, resourceRef }], - { - token, - }, - ) - )[0]; - if (decision.result === AuthorizeResult.DENY) { - throw new NotAllowedError('Unauthorized'); - } - - logger.info( - `entity ${entity.metadata.name} - azure site "${name}" is stopping...`, - ); - response.json( - await azureSitesApi.stop({ - subscription, - resourceGroup, - name, - }), - ); }, ); router.use(errorHandler()); diff --git a/plugins/azure-sites-backend/src/service/standaloneServer.ts b/plugins/azure-sites-backend/src/service/standaloneServer.ts index 8c64fa7b1b..202c1a128d 100644 --- a/plugins/azure-sites-backend/src/service/standaloneServer.ts +++ b/plugins/azure-sites-backend/src/service/standaloneServer.ts @@ -25,6 +25,7 @@ import { Logger } from 'winston'; import { AzureSitesApi } from '../api'; import { createRouter } from './router'; import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +import { CatalogClient } from '@backstage/catalog-client'; export interface ServerOptions { port: number; @@ -45,11 +46,13 @@ export async function startStandaloneServer( discovery, tokenManager, }); + const catalogApi = new CatalogClient({ discoveryApi: discovery }); logger.debug('Starting application server...'); const router = await createRouter({ logger, permissions, azureSitesApi: AzureSitesApi.fromConfig(config), + catalogApi, }); let service = createServiceBuilder(module) diff --git a/plugins/azure-sites-common/api-report.md b/plugins/azure-sites-common/api-report.md index 7eb88da1c8..31ada5b0ce 100644 --- a/plugins/azure-sites-common/api-report.md +++ b/plugins/azure-sites-common/api-report.md @@ -3,7 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { Entity } from '@backstage/catalog-model'; import { ResourcePermission } from '@backstage/plugin-permission-common'; // @public (undocumented) @@ -46,7 +45,7 @@ export type AzureSiteStartStopRequest = { subscription: string; resourceGroup: string; name: string; - entity?: Entity; + entityRef?: string; }; // (No @packageDocumentation comment for this package) diff --git a/plugins/azure-sites-common/src/types.ts b/plugins/azure-sites-common/src/types.ts index 7bab33cef5..dc540599cb 100644 --- a/plugins/azure-sites-common/src/types.ts +++ b/plugins/azure-sites-common/src/types.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; - /** @public */ export type AzureSite = { href: string; @@ -47,5 +45,5 @@ export type AzureSiteStartStopRequest = { subscription: string; resourceGroup: string; name: string; - entity?: Entity; + entityRef?: string; }; diff --git a/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts b/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts index 8c19f4a93d..b482e80d92 100644 --- a/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts +++ b/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts @@ -39,7 +39,7 @@ export class AzureSitesApiBackendClient implements AzureSitesApi { request.subscription }/${request.resourceGroup}/${request.name}/stop`; const { token: accessToken } = await this.identityApi.getCredentials(); - const entity = request.entity; + const entityRef = request.entityRef; await fetch(url, { method: 'POST', headers: { @@ -47,7 +47,7 @@ export class AzureSitesApiBackendClient implements AzureSitesApi { ...(accessToken && { Authorization: `Bearer ${accessToken}` }), }, body: JSON.stringify({ - entity, + entityRef, }), }); } @@ -56,7 +56,7 @@ export class AzureSitesApiBackendClient implements AzureSitesApi { request.subscription }/${request.resourceGroup}/${request.name}/start`; const { token: accessToken } = await this.identityApi.getCredentials(); - const entity = request.entity; + const entityRef = request.entityRef; await fetch(url, { method: 'POST', headers: { @@ -64,7 +64,7 @@ export class AzureSitesApiBackendClient implements AzureSitesApi { ...(accessToken && { Authorization: `Bearer ${accessToken}` }), }, body: JSON.stringify({ - entity, + entityRef, }), }); } diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx index 2095dc5391..2f891ec3d2 100644 --- a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx +++ b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx @@ -45,6 +45,7 @@ import { useApi } from '@backstage/core-plugin-api'; import { azureSiteApiRef } from '../../api'; import { useEntity } from '@backstage/plugin-catalog-react'; import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; +import { stringifyEntityRef } from '@backstage/catalog-model'; type States = 'Waiting' | 'Running' | 'Paused' | 'Failed' | 'Stopped'; type Kinds = 'app' | 'functionapp'; @@ -123,6 +124,7 @@ const ActionButtons = ({ }) => { const azureApi = useApi(azureSiteApiRef); const { entity } = useEntity(); + const entityRef = stringifyEntityRef(entity); const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); @@ -138,7 +140,7 @@ const ActionButtons = ({ name: value.name, resourceGroup: value.resourceGroup, subscription: value.subscription, - entity: entity, + entityRef: entityRef, }); onMenuItemClick('Starting, this may take some time...'); handleClose(); @@ -148,7 +150,7 @@ const ActionButtons = ({ name: value.name, resourceGroup: value.resourceGroup, subscription: value.subscription, - entity: entity, + entityRef: entityRef, }); onMenuItemClick('Stopping, this may take some time...'); handleClose(); diff --git a/yarn.lock b/yarn.lock index 8fe6c1c3ee..49042fa725 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4969,6 +4969,7 @@ __metadata: "@azure/arm-resourcegraph": ^4.2.1 "@azure/identity": ^4.0.0 "@backstage/backend-common": "workspace:^" + "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^"