(wip) refactor to more generic azure plugin
Signed-off-by: Wesley <wpattison08@gmail.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-azure-functions': minor
|
||||
'@backstage/plugin-azure-functions-backend': minor
|
||||
'@backstage/plugin-azure-functions-common': minor
|
||||
'@backstage/plugin-azure': minor
|
||||
'@backstage/plugin-azure-backend': minor
|
||||
'@backstage/plugin-azure-common': minor
|
||||
---
|
||||
|
||||
Azure Functions Plugin support based on annotation supplied by the entity.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title: Azure Functions
|
||||
author: FRISS
|
||||
authorUrl: https://friss.com
|
||||
category: Infrastructure
|
||||
description: View Azure Functions for your components in Backstage.
|
||||
documentation: https://github.com/backstage/backstage/tree/master/plugins/azure-functions
|
||||
iconUrl: img/azurefunctions-icon.svg
|
||||
npmPackageName: '@backstage/plugin-azure-functions'
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Azure
|
||||
author: FRISS
|
||||
authorUrl: https://friss.com
|
||||
category: Infrastructure
|
||||
description: View related Azure information for your components in Backstage.
|
||||
documentation: https://github.com/backstage/backstage/tree/master/plugins/azure
|
||||
iconUrl: img/azure-icon.svg
|
||||
npmPackageName: '@backstage/plugin-azure'
|
||||
@@ -0,0 +1,23 @@
|
||||
<svg aria-hidden="true" data-slug-id="azure" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="a" x1="-960.606" y1="283.397" x2="-1032.511" y2="70.972" gradientTransform="matrix(1 0 0 -1 1075 318)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#114a8b"/>
|
||||
<stop offset="1" stop-color="#0669bc"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="-938.144" y1="184.402" x2="-954.778" y2="178.778" gradientTransform="matrix(1 0 0 -1 1075 318)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-opacity=".3"/>
|
||||
<stop offset=".071" stop-opacity=".2"/>
|
||||
<stop offset=".321" stop-opacity=".1"/>
|
||||
<stop offset=".623" stop-opacity=".05"/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="c" x1="-947.292" y1="289.594" x2="-868.363" y2="79.308" gradientTransform="matrix(1 0 0 -1 1075 318)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#3ccbf4"/>
|
||||
<stop offset="1" stop-color="#2892df"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M89.158 18.266h69.238L86.523 231.224a11.041 11.041 0 01-10.461 7.51H22.179a11.023 11.023 0 01-10.445-14.548l66.963-198.41a11.04 11.04 0 0110.461-7.51z" fill="url(#a)"/>
|
||||
<path d="M189.77 161.104H79.976a5.083 5.083 0 00-3.468 8.8l70.552 65.847a11.091 11.091 0 007.567 2.983h62.167z" fill="#0078d4"/>
|
||||
<path d="M89.158 18.266a10.95 10.95 0 00-10.483 7.654L11.817 224.006a11.01 11.01 0 0010.393 14.728h55.274a11.814 11.814 0 009.069-7.714l13.33-39.29 47.625 44.418a11.267 11.267 0 007.089 2.586h61.937l-27.166-77.63-79.19.018 48.47-142.856z" fill="url(#b)"/>
|
||||
<path d="M177.592 25.764a11.023 11.023 0 00-10.444-7.498H89.984a11.024 11.024 0 0110.445 7.498l66.967 198.421a11.024 11.024 0 01-10.445 14.549h77.164a11.024 11.024 0 0010.444-14.549z" fill="url(#c)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -18,8 +18,8 @@
|
||||
"@backstage/plugin-airbrake": "^0.3.9-next.3",
|
||||
"@backstage/plugin-apache-airflow": "^0.2.2-next.3",
|
||||
"@backstage/plugin-api-docs": "^0.8.9-next.3",
|
||||
"@backstage/plugin-azure": "^0.0.0",
|
||||
"@backstage/plugin-azure-devops": "^0.2.0-next.3",
|
||||
"@backstage/plugin-azure-functions": "^0.0.0",
|
||||
"@backstage/plugin-badges": "^0.2.33-next.3",
|
||||
"@backstage/plugin-catalog-common": "^1.0.6-next.0",
|
||||
"@backstage/plugin-catalog-graph": "^0.2.21-next.2",
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
"@backstage/plugin-app-backend": "^0.3.36-next.3",
|
||||
"@backstage/plugin-auth-backend": "^0.16.0-next.3",
|
||||
"@backstage/plugin-auth-node": "^0.2.5-next.3",
|
||||
"@backstage/plugin-azure-backend": "^0.0.0",
|
||||
"@backstage/plugin-azure-devops-backend": "^0.3.15-next.2",
|
||||
"@backstage/plugin-azure-functions-backend": "^0.0.0",
|
||||
"@backstage/plugin-badges-backend": "^0.1.30-next.1",
|
||||
"@backstage/plugin-catalog-backend": "^1.4.0-next.3",
|
||||
"@backstage/plugin-code-coverage-backend": "^0.2.2-next.2",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Azure Functions Backend
|
||||
# Azure Backend
|
||||
|
||||
Simple plugin that proxies requests to the Azure Portal API through Azure SDK JavaScript libraries.
|
||||
|
||||
@@ -6,21 +6,20 @@ _Inspired by [roadie.io AWS Lamda plugin](https://roadie.io/backstage/plugins/aw
|
||||
|
||||
## Setup
|
||||
|
||||
The following sections will help you get the Azure Functions Backend plugin setup and running.
|
||||
The following sections will help you get the Azure Backend plugin setup and running.
|
||||
|
||||
### Configuration
|
||||
|
||||
The Azure Functions plugin requires the following YAML to be added to your app-config.yaml:
|
||||
The Azure plugin requires the following YAML to be added to your app-config.yaml:
|
||||
|
||||
```yaml
|
||||
azureFunctions:
|
||||
azure:
|
||||
domain:
|
||||
tenantId:
|
||||
clientId:
|
||||
clientSecret:
|
||||
allowedSubscriptions:
|
||||
- id:
|
||||
name:
|
||||
```
|
||||
|
||||
Configuration Details:
|
||||
@@ -28,26 +27,26 @@ Configuration Details:
|
||||
- `domain` can be found by visiting the [Directories + Subscriptions settings page](https://portal.azure.com/#settings/directory). Alternatively you can inspect the [Azure home](https://portal.azure.com/#home) URL - `https://portal.azure.com/#@<Your_Domain>/`.
|
||||
- `tenantId` can be found by visiting [Azure Directory Overview page](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade).
|
||||
- (Optional) `clientId` and `clientSecret` can be the same values you used for [Azure DevOps Backend](https://github.com/backstage/backstage/tree/master/plugins/azure-devops-backend) or [Azure Integration](https://backstage.io/docs/integrations/azure/org#app-registration) as long as this App Registration has permissions to read your function apps.
|
||||
- `allowedSubscriptions` is an array of `id` and `name` that will be used to iterate over and look for the specified functions' app. `id` and `name` can be found the [Subscriptions page](https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade).
|
||||
- (Optional) `allowedSubscriptions` is an array of `id` that will be used to iterate over and look for the specified functions' app. `id` can be found the [Subscriptions page](https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade).
|
||||
|
||||
### Integrating
|
||||
|
||||
Here's how to get the backend plugin up and running:
|
||||
|
||||
1. First we need to add the `@backstage/plugin-azure-functions-backend` package to your backend:
|
||||
1. First we need to add the `@backstage/plugin-azure-backend` package to your backend:
|
||||
|
||||
```sh
|
||||
# From the Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-azure-functions-backend
|
||||
yarn add --cwd packages/backend @backstage/plugin-azure-backend
|
||||
```
|
||||
|
||||
2. Then we will create a new file named `packages/backend/src/plugins/azure-functions.ts`, and add the following to it:
|
||||
2. Then we will create a new file named `packages/backend/src/plugins/azure.ts`, and add the following to it:
|
||||
|
||||
```ts
|
||||
import {
|
||||
createRouter,
|
||||
AzureWebManagementApi,
|
||||
} from '@backstage/plugin-azure-functions-backend';
|
||||
AzureSitesApi,
|
||||
} from '@backstage/plugin-azure-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
@@ -56,7 +55,7 @@ Here's how to get the backend plugin up and running:
|
||||
): Promise<Router> {
|
||||
return await createRouter({
|
||||
logger: env.logger,
|
||||
azureWebManagementApi: AzureWebManagementApi.fromConfig(env.config),
|
||||
azureSitesApi: AzureSitesApi.fromConfig(env.config),
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -64,23 +63,23 @@ Here's how to get the backend plugin up and running:
|
||||
3. Next we wire this into the overall backend router, edit `packages/backend/src/index.ts`:
|
||||
|
||||
```ts
|
||||
import azureFunctions from './plugins/azure-functions';
|
||||
import azure from './plugins/azure';
|
||||
|
||||
// Removed for clairty...
|
||||
|
||||
async function main() {
|
||||
// ...
|
||||
// Add this line under the other lines that follow the useHotMemoize pattern
|
||||
const azureFunctionsEnv = useHotMemoize(module, () =>
|
||||
createEnv('azureFunctions'),
|
||||
const azureEnv = useHotMemoize(module, () =>
|
||||
createEnv('azure'),
|
||||
);
|
||||
|
||||
// ...
|
||||
// Insert this line under the other lines that add their routers to apiRouter in the same way
|
||||
apiRouter.use('/azure-functions', await azureFunctions(azureFunctionsEnv));
|
||||
apiRouter.use('/azure', await azure(azureEnv));
|
||||
}
|
||||
```
|
||||
|
||||
4. Now run `yarn start-backend` from the repo root.
|
||||
|
||||
5. Finally, open `http://localhost:7007/api/azure-functions/health` in a browser, it should return `{"status":"ok"}`.
|
||||
5. Finally, open `http://localhost:7007/api/azure/health` in a browser, it should return `{"status":"ok"}`.
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-functions-backend",
|
||||
"name": "@backstage/plugin-azure-backend",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
@@ -17,11 +17,10 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/azure-functions-backend"
|
||||
"directory": "plugins/azure-backend"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage",
|
||||
"functions",
|
||||
"azure"
|
||||
],
|
||||
"scripts": {
|
||||
@@ -36,9 +35,9 @@
|
||||
"dependencies": {
|
||||
"@azure/arm-resourcegraph": "^4.2.1",
|
||||
"@azure/identity": "^2.1.0",
|
||||
"@backstage/backend-common": "^0.15.1-next.2",
|
||||
"@backstage/backend-common": "^0.15.1-next.3",
|
||||
"@backstage/config": "^1.0.2-next.0",
|
||||
"@backstage/plugin-azure-functions-common": "^0.0.0",
|
||||
"@backstage/plugin-azure-common": "^0.0.0",
|
||||
"@types/express": "*",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
+9
-12
@@ -15,23 +15,20 @@
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
azureFunctions: {
|
||||
azure: {
|
||||
/** @visibility backend */
|
||||
domain: string;
|
||||
/** @visibility backend */
|
||||
tenantId: string;
|
||||
/** @visibility backend */
|
||||
subscriptions?: [
|
||||
{
|
||||
id: string;
|
||||
},
|
||||
];
|
||||
/** @visibility backend */
|
||||
clientId?: string;
|
||||
/** @visibility secret */
|
||||
clientSecret?: string;
|
||||
/** @visibility backend */
|
||||
domain: string;
|
||||
/** @visibility backend */
|
||||
allowedSubscriptions: [
|
||||
{
|
||||
/** @visibility backend */
|
||||
name: string;
|
||||
/** @visibility backend */
|
||||
id: string;
|
||||
},
|
||||
];
|
||||
};
|
||||
}
|
||||
+17
-48
@@ -20,46 +20,16 @@ import {
|
||||
ClientSecretCredential,
|
||||
} from '@azure/identity';
|
||||
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
|
||||
import {
|
||||
AzureFunctionsAllowedSubscriptionsConfig,
|
||||
FunctionsListResponse,
|
||||
} from '@backstage/plugin-azure-functions-common';
|
||||
import { Site, SiteList } from '@backstage/plugin-azure-common';
|
||||
import { AzureConfig } from '../config';
|
||||
|
||||
/** @public */
|
||||
export class AzureFunctionsConfig {
|
||||
constructor(
|
||||
public readonly domain: string,
|
||||
public readonly allowedSubscriptions: AzureFunctionsAllowedSubscriptionsConfig[],
|
||||
public readonly tenantId: string,
|
||||
public readonly clientId?: string,
|
||||
public readonly clientSecret?: string,
|
||||
) {}
|
||||
|
||||
static fromConfig(config: Config): AzureFunctionsConfig {
|
||||
const azfConfig = config.getConfig('azureFunctions');
|
||||
|
||||
return new AzureFunctionsConfig(
|
||||
azfConfig.getString('domain'),
|
||||
azfConfig
|
||||
.getConfigArray('allowedSubscriptions')
|
||||
.map<AzureFunctionsAllowedSubscriptionsConfig>(as => ({
|
||||
id: as.getString('id'),
|
||||
name: as.getString('name'),
|
||||
})),
|
||||
azfConfig.getString('tenantId'),
|
||||
azfConfig.getOptionalString('clientId'),
|
||||
azfConfig.getOptionalString('clientSecret'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class AzureWebManagementApi {
|
||||
export class AzureSitesApi {
|
||||
private readonly baseHref = (domain: string) =>
|
||||
`https://portal.azure.com/#@${domain}/resource`;
|
||||
private readonly resourceGraphClient: ResourceGraphClient;
|
||||
private readonly client: ResourceGraphClient;
|
||||
|
||||
constructor(private readonly config: AzureFunctionsConfig) {
|
||||
constructor(private readonly config: AzureConfig) {
|
||||
const creds =
|
||||
config.clientId && config.clientSecret
|
||||
? new ClientSecretCredential(
|
||||
@@ -69,23 +39,19 @@ export class AzureWebManagementApi {
|
||||
)
|
||||
: new DefaultAzureCredential({ tenantId: config.tenantId });
|
||||
|
||||
this.resourceGraphClient = new ResourceGraphClient(creds);
|
||||
this.client = new ResourceGraphClient(creds);
|
||||
}
|
||||
|
||||
static fromConfig(config: Config): AzureWebManagementApi {
|
||||
return new AzureWebManagementApi(AzureFunctionsConfig.fromConfig(config));
|
||||
static fromConfig(config: Config): AzureSitesApi {
|
||||
return new AzureSitesApi(AzureConfig.fromConfig(config));
|
||||
}
|
||||
|
||||
async list({
|
||||
functionName,
|
||||
}: {
|
||||
functionName: string;
|
||||
}): Promise<FunctionsListResponse> {
|
||||
const items = [];
|
||||
async list({ name }: { name: string }): Promise<SiteList> {
|
||||
const items: Site[] = [];
|
||||
try {
|
||||
const result = await this.resourceGraphClient.resources({
|
||||
query: `resources | where type == 'microsoft.web/sites' | where name contains '${functionName}'`,
|
||||
subscriptions: this.config.allowedSubscriptions.map(s => s.id),
|
||||
const result = await this.client.resources({
|
||||
query: `resources | where type == 'microsoft.web/sites' | where name contains '${name}'`,
|
||||
subscriptions: this.config.subscriptions,
|
||||
});
|
||||
for (const v of result.data) {
|
||||
items.push({
|
||||
@@ -93,12 +59,15 @@ export class AzureWebManagementApi {
|
||||
logstreamHref: `${this.baseHref(
|
||||
this.config.domain,
|
||||
)}${v.id!}/logStream`,
|
||||
functionName: v.name!,
|
||||
name: v.name!,
|
||||
kind: v.kind!,
|
||||
resourceGroup: v.resourceGroup!,
|
||||
location: v.location!,
|
||||
lastModifiedDate: v.properties.lastModifiedTimeUtc!,
|
||||
usageState: v.properties.usageState!,
|
||||
state: v.properties.state!,
|
||||
containerSize: v.properties.containerSize!,
|
||||
tags: v.properties.tags!,
|
||||
});
|
||||
}
|
||||
} catch (ex) {
|
||||
@@ -14,5 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './AzureFunctionsApi';
|
||||
export * from './AzureFunctionsBackendClient';
|
||||
export { AzureSitesApi } from './AzureSitesApi';
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
/** @public */
|
||||
export class AzureConfig {
|
||||
constructor(
|
||||
public readonly domain: string,
|
||||
public readonly tenantId: string,
|
||||
public readonly subscriptions?: string[],
|
||||
public readonly clientId?: string,
|
||||
public readonly clientSecret?: string,
|
||||
) {}
|
||||
|
||||
static fromConfig(config: Config): AzureConfig {
|
||||
const azConfig = config.getConfig('azure');
|
||||
|
||||
return new AzureConfig(
|
||||
azConfig.getString('domain'),
|
||||
azConfig.getString('tenantId'),
|
||||
azConfig
|
||||
.getOptionalConfigArray('subscriptions')
|
||||
?.map<string>(as => as.getString('id')),
|
||||
azConfig.getOptionalString('clientId'),
|
||||
azConfig.getOptionalString('clientSecret'),
|
||||
);
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -19,19 +19,19 @@ import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
import { AzureWebManagementApi } from '../api';
|
||||
import { AzureSitesApi } from '../api';
|
||||
|
||||
/** @public */
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
azureWebManagementApi: AzureWebManagementApi;
|
||||
azureSitesApi: AzureSitesApi;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { logger, azureWebManagementApi } = options;
|
||||
const { logger, azureSitesApi } = options;
|
||||
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
@@ -40,11 +40,11 @@ export async function createRouter(
|
||||
logger.info('PONG!');
|
||||
response.send({ status: 'ok' });
|
||||
});
|
||||
router.get('/list/:functionName', async (request, response) => {
|
||||
const { functionName } = request.params;
|
||||
router.get('/list/:name', async (request, response) => {
|
||||
const { name } = request.params;
|
||||
response.json(
|
||||
await azureWebManagementApi.list({
|
||||
functionName: functionName,
|
||||
await azureSitesApi.list({
|
||||
name: name,
|
||||
}),
|
||||
);
|
||||
});
|
||||
+4
-4
@@ -20,7 +20,7 @@ import {
|
||||
} from '@backstage/backend-common';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { AzureWebManagementApi } from '../api';
|
||||
import { AzureSitesApi } from '../api';
|
||||
import { createRouter } from './router';
|
||||
|
||||
export interface ServerOptions {
|
||||
@@ -32,17 +32,17 @@ export interface ServerOptions {
|
||||
export async function startStandaloneServer(
|
||||
options: ServerOptions,
|
||||
): Promise<Server> {
|
||||
const logger = options.logger.child({ service: 'azure-functions-backend' });
|
||||
const logger = options.logger.child({ service: 'azure-backend' });
|
||||
const config = await loadBackendConfig({ logger, argv: process.argv });
|
||||
logger.debug('Starting application server...');
|
||||
const router = await createRouter({
|
||||
logger,
|
||||
azureWebManagementApi: AzureWebManagementApi.fromConfig(config),
|
||||
azureSitesApi: AzureSitesApi.fromConfig(config),
|
||||
});
|
||||
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/azure-functions-backend', router);
|
||||
.addRouter('/azure-backend', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
@@ -1,31 +1,26 @@
|
||||
## API Report File for "@backstage/plugin-azure-functions-common"
|
||||
## API Report File for "@backstage/plugin-azure-common"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
// @public (undocumented)
|
||||
export interface AzureFunctionsAllowedSubscriptionsConfig {
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type FunctionsData = {
|
||||
export type Site = {
|
||||
href: string;
|
||||
logstreamHref: string;
|
||||
functionName: string;
|
||||
name: string;
|
||||
kind: string;
|
||||
location: string;
|
||||
resourceGroup: string;
|
||||
state: string;
|
||||
usageState: string;
|
||||
containerSize: number;
|
||||
lastModifiedDate: Date;
|
||||
lastModifiedDate: string;
|
||||
tags: {};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type FunctionsListResponse = {
|
||||
items: FunctionsData[];
|
||||
export type SiteList = {
|
||||
items: Site[];
|
||||
};
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-functions-common",
|
||||
"description": "Common functionalities for the azure-functions plugin",
|
||||
"name": "@backstage/plugin-azure-common",
|
||||
"description": "Common functionalities for the azure plugin",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
@@ -18,7 +18,7 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/azure-functions-common"
|
||||
"directory": "plugins/azure-common"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
@@ -15,24 +15,21 @@
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export interface AzureFunctionsAllowedSubscriptionsConfig {
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type FunctionsData = {
|
||||
export type Site = {
|
||||
href: string;
|
||||
logstreamHref: string;
|
||||
functionName: string;
|
||||
name: string;
|
||||
kind: string;
|
||||
location: string;
|
||||
resourceGroup: string;
|
||||
state: string;
|
||||
usageState: string;
|
||||
containerSize: number;
|
||||
lastModifiedDate: Date;
|
||||
lastModifiedDate: string;
|
||||
tags: {};
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type FunctionsListResponse = {
|
||||
items: FunctionsData[];
|
||||
export type SiteList = {
|
||||
items: Site[];
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
## API Report File for "@backstage/plugin-azure-functions-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { AzureFunctionsAllowedSubscriptionsConfig } from '@backstage/plugin-azure-functions-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { FunctionsListResponse } from '@backstage/plugin-azure-functions-common';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzureFunctionsConfig {
|
||||
constructor(
|
||||
domain: string,
|
||||
allowedSubscriptions: AzureFunctionsAllowedSubscriptionsConfig[],
|
||||
tenantId: string,
|
||||
clientId?: string | undefined,
|
||||
clientSecret?: string | undefined,
|
||||
);
|
||||
// (undocumented)
|
||||
readonly allowedSubscriptions: AzureFunctionsAllowedSubscriptionsConfig[];
|
||||
// (undocumented)
|
||||
readonly clientId?: string | undefined;
|
||||
// (undocumented)
|
||||
readonly clientSecret?: string | undefined;
|
||||
// (undocumented)
|
||||
readonly domain: string;
|
||||
// (undocumented)
|
||||
static fromConfig(config: Config): AzureFunctionsConfig;
|
||||
// (undocumented)
|
||||
readonly tenantId: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzureWebManagementApi {
|
||||
constructor(config: AzureFunctionsConfig);
|
||||
// (undocumented)
|
||||
static fromConfig(config: Config): AzureWebManagementApi;
|
||||
// (undocumented)
|
||||
list({
|
||||
functionName,
|
||||
}: {
|
||||
functionName: string;
|
||||
}): Promise<FunctionsListResponse>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
azureWebManagementApi: AzureWebManagementApi;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -1,64 +0,0 @@
|
||||
## API Report File for "@backstage/plugin-azure-functions"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { FunctionsListResponse } from '@backstage/plugin-azure-functions-common';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public (undocumented)
|
||||
export type AzureFunctionsApi = {
|
||||
list: ({
|
||||
functionName,
|
||||
}: {
|
||||
functionName: string;
|
||||
}) => Promise<FunctionsListResponse>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const azureFunctionsApiRef: ApiRef<AzureFunctionsApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzureFunctionsBackendClient implements AzureFunctionsApi {
|
||||
constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
});
|
||||
// (undocumented)
|
||||
list({
|
||||
functionName,
|
||||
}: {
|
||||
functionName: string;
|
||||
}): Promise<FunctionsListResponse>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const AzureFunctionsOverviewWidget: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const azureFunctionsPlugin: BackstagePlugin<
|
||||
{
|
||||
entityContent: RouteRef<undefined>;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityAzureFunctionsOverviewCard: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const entityContentRouteRef: RouteRef<undefined>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const isAzureFunctionsAvailable: (entity: Entity) => string | undefined;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -1,12 +1,12 @@
|
||||
# Azure Functions Plugin
|
||||
# Azure Plugin
|
||||
|
||||

|
||||

|
||||
|
||||
_Inspired by [roadie.io AWS Lamda plugin](https://roadie.io/backstage/plugins/aws-lambda/)_
|
||||
|
||||
## Features
|
||||
|
||||
- Azure Functions overview table
|
||||
- Azure overview table
|
||||
|
||||
## Plugin Setup
|
||||
|
||||
@@ -14,17 +14,17 @@ The following sections will help you get the Azure Functions plugin setup and ru
|
||||
|
||||
### Azure Functions Backend
|
||||
|
||||
You need to set up the Azure Functions backend plugin before you move forward with any of these steps if you haven't already.
|
||||
You need to set up the Azure backend plugin before you move forward with any of these steps if you haven't already.
|
||||
|
||||
### Entity Annotation
|
||||
|
||||
To be able to use the Azure Functions plugin you need to add the following annotation to any entities you want to use it with:
|
||||
To be able to use the Azure plugin you need to add the following annotation to any entities you want to use it with:
|
||||
|
||||
```yaml
|
||||
portal.azure.com/functions-name: <function-name>
|
||||
portal.azure.com/resource-name: <resource-name>
|
||||
```
|
||||
|
||||
`<function-name>` can be an exact or partial name for the functions' app.
|
||||
`<resource-name>` can be an exact or partial name for the site.
|
||||
|
||||
Example of Partial Matching:
|
||||
|
||||
@@ -39,7 +39,7 @@ func-testapp-us
|
||||
The annotation you will use to have the three functions' app appear in the overview table would look like this:
|
||||
|
||||
```yaml
|
||||
portal.azure.com/functions-name: func-testapp
|
||||
portal.azure.com/resource-name: func-testapp
|
||||
```
|
||||
|
||||
### Install the component
|
||||
@@ -47,22 +47,22 @@ portal.azure.com/functions-name: func-testapp
|
||||
1. Install the plugin in the `packages/app` directory
|
||||
|
||||
```sh
|
||||
yarn add @backstage/plugin-azure-functions
|
||||
yarn add @backstage/plugin-azure
|
||||
```
|
||||
|
||||
2. Add widget component to your Backstage instance:
|
||||
|
||||
```ts
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import { EntityAzureFunctionsOverviewCard, isAzureFunctionsAvailable } from '@backstage/plugin-azure-functions';
|
||||
import { EntityAzureSitesOverviewWidget, isAzureResourceNameAvailable } from '@backstage/plugin-azure';
|
||||
|
||||
...
|
||||
|
||||
const serviceEntityPage = (
|
||||
<EntityLayout>
|
||||
//...
|
||||
<EntityLayout.Route if={e => Boolean(isAzureFunctionsAvailable(e))} path="/azure-functions" title="Azure Functions">
|
||||
<EntityAzureFunctionsOverviewCard />
|
||||
<EntityLayout.Route if={e => Boolean(isAzureResourceNameAvailable(e))} path="/azure" title="Azure">
|
||||
<EntityAzureSitesOverviewWidget />
|
||||
</EntityLayout.Route>
|
||||
//...
|
||||
</EntityLayout>
|
||||
@@ -15,6 +15,6 @@
|
||||
*/
|
||||
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { azureFunctionsPlugin } from '../src/plugin';
|
||||
import { azureSitesPlugin } from '../src/plugin';
|
||||
|
||||
createDevApp().registerPlugin(azureFunctionsPlugin).render();
|
||||
createDevApp().registerPlugin(azureSitesPlugin).render();
|
||||
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-functions",
|
||||
"name": "@backstage/plugin-azure",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
@@ -17,11 +17,10 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/azure-functions"
|
||||
"directory": "plugins/azure"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage",
|
||||
"functions",
|
||||
"azure"
|
||||
],
|
||||
"scripts": {
|
||||
@@ -37,7 +36,7 @@
|
||||
"@backstage/catalog-model": "^1.1.1-next.0",
|
||||
"@backstage/core-components": "^0.11.1-next.2",
|
||||
"@backstage/core-plugin-api": "^1.0.6-next.2",
|
||||
"@backstage/plugin-azure-functions-common": "^0.0.0",
|
||||
"@backstage/plugin-azure-common": "^0.0.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.4-next.1",
|
||||
"@backstage/theme": "^0.2.16",
|
||||
"@material-ui/core": "^4.9.13",
|
||||
+5
-9
@@ -15,18 +15,14 @@
|
||||
*/
|
||||
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
import { FunctionsListResponse } from '@backstage/plugin-azure-functions-common';
|
||||
import { SiteList } from '@backstage/plugin-azure-common';
|
||||
|
||||
/** @public */
|
||||
export const azureFunctionsApiRef = createApiRef<AzureFunctionsApi>({
|
||||
id: 'plugin.azurefunctions.service',
|
||||
export const azureSiteApiRef = createApiRef<AzureSitesApi>({
|
||||
id: 'plugin.azure.service',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export type AzureFunctionsApi = {
|
||||
list: ({
|
||||
functionName,
|
||||
}: {
|
||||
functionName: string;
|
||||
}) => Promise<FunctionsListResponse>;
|
||||
export type AzureSitesApi = {
|
||||
list: ({ name }: { name: string }) => Promise<SiteList>;
|
||||
};
|
||||
+5
-11
@@ -14,12 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AzureFunctionsApi } from './AzureFunctionsApi';
|
||||
import { FunctionsListResponse } from '@backstage/plugin-azure-functions-common';
|
||||
import { AzureSitesApi } from './AzureSitesApi';
|
||||
import { SiteList } from '@backstage/plugin-azure-common';
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export class AzureFunctionsBackendClient implements AzureFunctionsApi {
|
||||
export class AzureSitesApiBackendClient implements AzureSitesApi {
|
||||
private readonly identityApi: IdentityApi;
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
constructor(options: {
|
||||
@@ -30,15 +30,9 @@ export class AzureFunctionsBackendClient implements AzureFunctionsApi {
|
||||
this.identityApi = options.identityApi;
|
||||
}
|
||||
|
||||
async list({
|
||||
functionName,
|
||||
}: {
|
||||
functionName: string;
|
||||
}): Promise<FunctionsListResponse> {
|
||||
async list({ name }: { name: string }): Promise<SiteList> {
|
||||
try {
|
||||
const url = `${await this.discoveryApi.getBaseUrl(
|
||||
'azure-functions',
|
||||
)}/list/${functionName}`;
|
||||
const url = `${await this.discoveryApi.getBaseUrl('azure')}/list/${name}`;
|
||||
const { token: accessToken } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
@@ -14,7 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
AzureWebManagementApi,
|
||||
AzureFunctionsConfig,
|
||||
} from './AzureWebManagementApi';
|
||||
export * from './AzureSitesApi';
|
||||
export * from './AzureSitesApiBackendClient';
|
||||
+20
-18
@@ -16,9 +16,9 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useFunctions } from '../../hooks/useFunctions';
|
||||
import { useSites } from '../../hooks/useSites';
|
||||
import {
|
||||
AZURE_FUNCTIONS_ANNOTATION,
|
||||
AZURE_RESOURCE_NAME_ANNOTATION,
|
||||
useServiceEntityAnnotations,
|
||||
} from '../../hooks/useServiceEntityAnnotations';
|
||||
import {
|
||||
@@ -27,48 +27,50 @@ import {
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core-components';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { OverviewTable } from '../OverviewTableComponent/OverviewTable';
|
||||
import { AzureSitesOverviewTable } from '../AzureSitesOverviewTableComponent/AzureSitesOverviewTable';
|
||||
|
||||
/** @public */
|
||||
export const isAzureFunctionsAvailable = (entity: Entity) =>
|
||||
entity?.metadata.annotations?.[AZURE_FUNCTIONS_ANNOTATION];
|
||||
export const isAzureResourceNameAvailable = (entity: Entity) =>
|
||||
entity?.metadata.annotations?.[AZURE_RESOURCE_NAME_ANNOTATION];
|
||||
|
||||
const AzureFunctionsOverview = ({ entity }: { entity: Entity }) => {
|
||||
const { functionsName } = useServiceEntityAnnotations(entity);
|
||||
const AzureSitesOverview = ({ entity }: { entity: Entity }) => {
|
||||
const { resourceName } = useServiceEntityAnnotations(entity);
|
||||
|
||||
const [functionsData] = useFunctions({
|
||||
functionsName,
|
||||
const [sites] = useSites({
|
||||
name: resourceName,
|
||||
});
|
||||
|
||||
if (functionsData.error) {
|
||||
if (sites.error) {
|
||||
return (
|
||||
<div>
|
||||
<ResponseErrorPanel error={functionsData.error} />
|
||||
<ResponseErrorPanel error={sites.error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OverviewTable
|
||||
data={functionsData.data?.items ?? []}
|
||||
loading={functionsData.loading}
|
||||
<AzureSitesOverviewTable
|
||||
data={sites.data?.items ?? []}
|
||||
loading={sites.loading}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const AzureFunctionsOverviewWidget = () => {
|
||||
export const AzureSitesOverviewWidget = () => {
|
||||
const { entity } = useEntity();
|
||||
|
||||
if (!isAzureFunctionsAvailable(entity)) {
|
||||
if (!isAzureResourceNameAvailable(entity)) {
|
||||
return (
|
||||
<MissingAnnotationEmptyState annotation={AZURE_FUNCTIONS_ANNOTATION} />
|
||||
<MissingAnnotationEmptyState
|
||||
annotation={AZURE_RESOURCE_NAME_ANNOTATION}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<AzureFunctionsOverview entity={entity} />
|
||||
<AzureSitesOverview entity={entity} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
+14
-17
@@ -29,9 +29,9 @@ import {
|
||||
} from '@backstage/test-utils';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { DateTime } from 'luxon';
|
||||
import { functionResponseMock } from '../../mocks/mocks';
|
||||
import { azureFunctionsApiRef } from '../..';
|
||||
import { OverviewTable } from './OverviewTable';
|
||||
import { siteMock } from '../../mocks/mocks';
|
||||
import { azureSiteApiRef } from '../..';
|
||||
import { AzureSitesOverviewTable } from './AzureSitesOverviewTable';
|
||||
|
||||
const errorApiMock = { post: jest.fn(), error$: jest.fn() };
|
||||
const identityApiMock = (getCredentials: any) => ({
|
||||
@@ -40,7 +40,7 @@ const identityApiMock = (getCredentials: any) => ({
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials,
|
||||
});
|
||||
const azureFunctionsApiMock = {};
|
||||
const azureSitesApiMock = {};
|
||||
|
||||
const config = {
|
||||
getString: (_: string) => 'https://test-url',
|
||||
@@ -49,18 +49,18 @@ const config = {
|
||||
const apis: [AnyApiRef, Partial<unknown>][] = [
|
||||
[errorApiRef, errorApiMock],
|
||||
[configApiRef, config],
|
||||
[azureFunctionsApiRef, azureFunctionsApiMock],
|
||||
[azureSiteApiRef, azureSitesApiMock],
|
||||
[identityApiRef, identityApiMock],
|
||||
];
|
||||
|
||||
describe('AzureFunctionsOverviewWidget', () => {
|
||||
describe('AzureSitesOverviewWidget', () => {
|
||||
const worker = setupServer();
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get('/list/func-mock', (_, res, ctx) => {
|
||||
res(ctx.json(functionResponseMock));
|
||||
res(ctx.json(siteMock));
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -68,21 +68,18 @@ describe('AzureFunctionsOverviewWidget', () => {
|
||||
it('should display an overview table with the data from the requests', async () => {
|
||||
const rendered = render(
|
||||
<TestApiProvider apis={apis}>
|
||||
<OverviewTable data={[functionResponseMock]} loading={false} />
|
||||
<AzureSitesOverviewTable data={[siteMock]} loading={false} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(
|
||||
await rendered.findByText(functionResponseMock.functionName),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
await rendered.findByText(functionResponseMock.state),
|
||||
).toBeInTheDocument();
|
||||
expect(await rendered.findByText(siteMock.name)).toBeInTheDocument();
|
||||
expect(await rendered.findByText(siteMock.kind)).toBeInTheDocument();
|
||||
expect(await rendered.findByText(siteMock.state)).toBeInTheDocument();
|
||||
expect(
|
||||
await rendered.findByText(
|
||||
DateTime.fromISO(
|
||||
functionResponseMock.lastModifiedDate.toString(),
|
||||
).toLocaleString(DateTime.DATETIME_MED),
|
||||
DateTime.fromISO(siteMock.lastModifiedDate).toLocaleString(
|
||||
DateTime.DATETIME_MED,
|
||||
),
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
+18
-14
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Card, Link, LinearProgress } from '@material-ui/core';
|
||||
import { FunctionsData } from '@backstage/plugin-azure-functions-common';
|
||||
import { Site } from '@backstage/plugin-azure-common';
|
||||
import { Table, TableColumn } from '@backstage/core-components';
|
||||
import FlashOnIcon from '@material-ui/icons/FlashOn';
|
||||
import { DateTime } from 'luxon';
|
||||
@@ -47,42 +47,46 @@ const State = ({ value }: { value: States }) => {
|
||||
);
|
||||
};
|
||||
|
||||
type FunctionTableProps = {
|
||||
data: FunctionsData[];
|
||||
type TableProps = {
|
||||
data: Site[];
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_COLUMNS: TableColumn<FunctionsData>[] = [
|
||||
const DEFAULT_COLUMNS: TableColumn<Site>[] = [
|
||||
{
|
||||
title: 'name',
|
||||
highlight: true,
|
||||
render: (func: FunctionsData) => {
|
||||
render: (func: Site) => {
|
||||
return (
|
||||
<Link href={func.href} target="_blank">
|
||||
{func.functionName}
|
||||
{func.name}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'kind',
|
||||
render: (func: Site) => func.kind ?? 'unknown',
|
||||
},
|
||||
{
|
||||
title: 'location',
|
||||
render: (func: FunctionsData) => func.location ?? 'unknown',
|
||||
render: (func: Site) => func.location ?? 'unknown',
|
||||
},
|
||||
{
|
||||
title: 'status',
|
||||
render: (func: FunctionsData) => <State value={func.state as States} />,
|
||||
render: (func: Site) => <State value={func.state as States} />,
|
||||
},
|
||||
{
|
||||
title: 'last modified',
|
||||
render: (func: FunctionsData) =>
|
||||
DateTime.fromISO(func.lastModifiedDate.toString()).toLocaleString(
|
||||
render: (func: Site) =>
|
||||
DateTime.fromISO(func.lastModifiedDate).toLocaleString(
|
||||
DateTime.DATETIME_MED,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'logs',
|
||||
align: 'right',
|
||||
render: (func: FunctionsData) => {
|
||||
render: (func: Site) => {
|
||||
return (
|
||||
<Link href={func.logstreamHref} target="_blank">
|
||||
View Logs
|
||||
@@ -93,8 +97,8 @@ const DEFAULT_COLUMNS: TableColumn<FunctionsData>[] = [
|
||||
];
|
||||
|
||||
/** @public */
|
||||
export const OverviewTable = ({ data, loading }: FunctionTableProps) => {
|
||||
const columns: TableColumn<FunctionsData>[] = [...DEFAULT_COLUMNS];
|
||||
export const AzureSitesOverviewTable = ({ data, loading }: TableProps) => {
|
||||
const columns: TableColumn<Site>[] = [...DEFAULT_COLUMNS];
|
||||
const tableStyle = {
|
||||
minWidth: '0',
|
||||
width: '100%',
|
||||
@@ -107,7 +111,7 @@ export const OverviewTable = ({ data, loading }: FunctionTableProps) => {
|
||||
<Box display="flex" alignItems="center">
|
||||
<FlashOnIcon style={{ fontSize: 30 }} />
|
||||
<Box mr={1} />
|
||||
Azure Functions
|
||||
Azure Sites
|
||||
</Box>
|
||||
}
|
||||
options={{ paging: true, search: false, pageSize: 10 }}
|
||||
+4
-4
@@ -16,12 +16,12 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export const AZURE_FUNCTIONS_ANNOTATION = 'portal.azure.com/functions-name';
|
||||
export const AZURE_RESOURCE_NAME_ANNOTATION = 'portal.azure.com/resource-name';
|
||||
export const useServiceEntityAnnotations = (entity: Entity) => {
|
||||
const functionsName =
|
||||
entity?.metadata.annotations?.[AZURE_FUNCTIONS_ANNOTATION] ?? '';
|
||||
const resourceName =
|
||||
entity?.metadata.annotations?.[AZURE_RESOURCE_NAME_ANNOTATION] ?? '';
|
||||
|
||||
return {
|
||||
functionsName,
|
||||
resourceName,
|
||||
};
|
||||
};
|
||||
+11
-11
@@ -16,35 +16,35 @@
|
||||
|
||||
import useAsyncRetry from 'react-use/lib/useAsyncRetry';
|
||||
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
|
||||
import { FunctionsListResponse } from '@backstage/plugin-azure-functions-common';
|
||||
import { azureFunctionsApiRef } from '../api';
|
||||
import { SiteList } from '@backstage/plugin-azure-common';
|
||||
import { azureSiteApiRef } from '../api';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function useFunctions({ functionsName }: { functionsName: string }) {
|
||||
const azureFunctionsApi = useApi(azureFunctionsApiRef);
|
||||
export function useSites({ name }: { name: string }) {
|
||||
const azureApi = useApi(azureSiteApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
const list = useCallback(
|
||||
async () => {
|
||||
return await azureFunctionsApi.list({
|
||||
functionName: functionsName,
|
||||
return await azureApi.list({
|
||||
name: name,
|
||||
});
|
||||
},
|
||||
[functionsName], // eslint-disable-line react-hooks/exhaustive-deps
|
||||
[name], // eslint-disable-line react-hooks/exhaustive-deps
|
||||
);
|
||||
const {
|
||||
loading,
|
||||
value: data,
|
||||
error,
|
||||
retry,
|
||||
} = useAsyncRetry<FunctionsListResponse | null>(async () => {
|
||||
} = useAsyncRetry<SiteList | null>(async () => {
|
||||
try {
|
||||
const azureFunction = await list();
|
||||
return azureFunction;
|
||||
const sites = await list();
|
||||
return sites;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
if (e?.message === 'MissingAzureBackendException') {
|
||||
errorApi.post(new Error('Please add azure-functions-backend plugin'));
|
||||
errorApi.post(new Error('Please add azure-backend plugin'));
|
||||
return null;
|
||||
}
|
||||
errorApi.post(e);
|
||||
@@ -16,4 +16,4 @@
|
||||
|
||||
export * from './plugin';
|
||||
export * from './api';
|
||||
export * from './components/AzureFunctionsOverviewComponent/AzureFunctionsOverview';
|
||||
export * from './components/AzureSitesOverviewComponent/AzureSitesOverview';
|
||||
@@ -14,15 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FunctionsData } from '@backstage/plugin-azure-functions-common';
|
||||
import { Site } from '@backstage/plugin-azure-common';
|
||||
|
||||
export const entityMock = {
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
'portal.azure.com/functions-name': 'func-mock',
|
||||
'portal.azure.com/resource-name': 'func-mock',
|
||||
},
|
||||
name: 'sample-azure-function-service',
|
||||
name: 'sample-azure-service',
|
||||
description: 'A service for testing Backstage functionality.',
|
||||
uid: 'c009b513-d053-4b3f-9429-8433a145e943',
|
||||
},
|
||||
@@ -36,14 +36,19 @@ export const entityMock = {
|
||||
};
|
||||
|
||||
// https://management.azure.com/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Web/sites/{{functionsName}}?api-version=2022-03-01
|
||||
export const functionResponseMock: FunctionsData = {
|
||||
functionName: 'func-mock',
|
||||
export const siteMock: Site = {
|
||||
name: 'func-mock',
|
||||
kind: 'functionapp',
|
||||
resourceGroup: 'mock-resourcegroup',
|
||||
tags: {
|
||||
isMock: true,
|
||||
},
|
||||
location: 'West Europe',
|
||||
state: 'Running',
|
||||
href: 'https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock',
|
||||
logstreamHref:
|
||||
'https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock/logStream',
|
||||
usageState: 'Normal',
|
||||
lastModifiedDate: new Date('2022-09-02T11:09:58.9033333'),
|
||||
lastModifiedDate: '2022-09-02T11:09:58.9033333',
|
||||
containerSize: 100,
|
||||
};
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { azureFunctionsPlugin } from './plugin';
|
||||
import { azureSitesPlugin } from './plugin';
|
||||
|
||||
describe('azure-functions', () => {
|
||||
describe('azure', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(azureFunctionsPlugin).toBeDefined();
|
||||
expect(azureSitesPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -22,25 +22,25 @@ import {
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { azureFunctionsApiRef, AzureFunctionsBackendClient } from './api';
|
||||
import { azureSiteApiRef, AzureSitesApiBackendClient } from './api';
|
||||
|
||||
/** @public */
|
||||
export const entityContentRouteRef = createRouteRef({
|
||||
id: 'Azure Functions Entity Content',
|
||||
id: 'Azure Entity Content',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const azureFunctionsPlugin = createPlugin({
|
||||
id: 'azure-functions',
|
||||
export const azureSitesPlugin = createPlugin({
|
||||
id: 'azure',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: azureFunctionsApiRef,
|
||||
api: azureSiteApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
identityApi: identityApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new AzureFunctionsBackendClient({ discoveryApi, identityApi }),
|
||||
new AzureSitesApiBackendClient({ discoveryApi, identityApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
@@ -49,14 +49,14 @@ export const azureFunctionsPlugin = createPlugin({
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const EntityAzureFunctionsOverviewCard = azureFunctionsPlugin.provide(
|
||||
export const EntityAzureSitesOverviewWidget = azureSitesPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityAzureFunctionsOverviewCard',
|
||||
name: 'EntityAzureSitesOverviewWidget',
|
||||
component: {
|
||||
lazy: () =>
|
||||
import(
|
||||
'./components/AzureFunctionsOverviewComponent/AzureFunctionsOverview'
|
||||
).then(m => m.AzureFunctionsOverviewWidget),
|
||||
'./components/AzureSitesOverviewComponent/AzureSitesOverview'
|
||||
).then(m => m.AzureSitesOverviewWidget),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -17,5 +17,5 @@
|
||||
import { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'azure-functions',
|
||||
id: 'azure',
|
||||
});
|
||||
Reference in New Issue
Block a user