From b4a752fc5adb5e2218bd0c0ce5ac8011871e53f0 Mon Sep 17 00:00:00 2001 From: Xiaofu Huang Date: Tue, 27 Jun 2023 17:28:57 +0800 Subject: [PATCH] refactor: remove apim remaining code --- .github/CODEOWNERS | 1 - packages/cli/tests/commonlib/apimValidator.ts | 450 ------------------ packages/cli/tests/commonlib/constants.ts | 11 - packages/cli/tests/commonlib/index.ts | 1 - packages/cli/tests/commonlib/utilities.ts | 27 -- packages/fx-core/.nycrc | 1 - packages/fx-core/src/component/constants.ts | 24 - .../fx-core/src/component/registerService.ts | 1 - .../templates/plugins/resource/apim/README.md | 92 ---- 9 files changed, 608 deletions(-) delete mode 100644 packages/cli/tests/commonlib/apimValidator.ts delete mode 100644 packages/fx-core/templates/plugins/resource/apim/README.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fe736c0ac0..6bec98265f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -32,7 +32,6 @@ /packages/cli/src/cmds/preview @swatDong @kuojianlu @qinezh @a1exwang /packages/cli/tests/e2e/aad @KennethBWSong @adashen @SLdragon /packages/cli/tests/e2e/collaboration @KennethBWSong @adashen @SLdragon -/packages/cli/tests/e2e/apim @XiaofuHuang @dooriya @kimizhu /packages/cli/tests/e2e/frontend @hund030 @eriolchan @huimiu /packages/cli/tests/e2e/bot @JerryYangKai @eriolchan @Siglud @Yukun-dong /packages/cli/tests/e2e/scaffold @hund030 @eriolchan @huimiu diff --git a/packages/cli/tests/commonlib/apimValidator.ts b/packages/cli/tests/commonlib/apimValidator.ts deleted file mode 100644 index 4aecdefe39..0000000000 --- a/packages/cli/tests/commonlib/apimValidator.ts +++ /dev/null @@ -1,450 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import axios, { AxiosInstance } from "axios"; -import * as chai from "chai"; -import { ApiManagementClient } from "@azure/arm-apimanagement"; -import fs from "fs-extra"; -import md5 from "md5"; -import { ResourceManagementClient } from "@azure/arm-resources"; -import { AzureAccountProvider, M365TokenProvider } from "@microsoft/teamsfx-api"; -import { - getApimServiceNameFromResourceId, - getAuthServiceNameFromResourceId, - getproductNameFromResourceId, - getResourceGroupNameFromResourceId, -} from "./utilities"; -import { PluginId, StateConfigKey } from "./constants"; -import { GraphScopes } from "@microsoft/teamsfx-core"; - -export class ApimValidator { - static apimClient?: ApiManagementClient; - static resourceGroupClient?: ResourceManagementClient; - static axiosInstance?: AxiosInstance; - - public static async init( - subscriptionId: string, - azureAccountProvider: AzureAccountProvider, - m365TokenProvider: M365TokenProvider - ): Promise { - const tokenCredential = await azureAccountProvider.getIdentityCredentialAsync(); - this.apimClient = new ApiManagementClient(tokenCredential!, subscriptionId); - this.resourceGroupClient = new ResourceManagementClient(tokenCredential!, subscriptionId); - const graphTokenRes = await m365TokenProvider.getAccessToken({ scopes: GraphScopes }); - const graphToken = graphTokenRes.isOk() ? graphTokenRes.value : undefined; - this.axiosInstance = axios.create({ - baseURL: "https://graph.microsoft.com/v1.0", - headers: { - authorization: `Bearer ${graphToken}`, - "content-type": "application/json", - }, - }); - } - - public static async prepareApiManagementService( - resourceGroupName: string, - serviceName: string - ): Promise { - await this.resourceGroupClient?.resourceGroups.createOrUpdate(resourceGroupName, { - location: "eastus", - }); - await this.apimClient?.apiManagementService.beginCreateOrUpdateAndWait( - resourceGroupName, - serviceName, - { - publisherName: "teamsfx-test@microsoft.com", - publisherEmail: "teamsfx-test@microsoft.com", - sku: { - name: "Consumption", - capacity: 0, - }, - location: "eastus", - } - ); - } - - public static async validateProvision(ctx: any): Promise { - console.log("Start validate apim provision."); - const config = new Config(ctx); - // chai.assert.isNotEmpty(config?.resourceNameSuffix); - chai.assert.isNotEmpty(config?.serviceResourceId); - chai.assert.isNotEmpty(config?.productResourceId); - chai.assert.isNotEmpty(config?.authServerResourceId); - chai.assert.isNotEmpty(config?.apimClientAADObjectId); - chai.assert.isNotEmpty(config?.apimClientAADClientId); - chai.assert.isNotEmpty(config?.apimClientAADClientSecret); - await this.validateApimService(config); - await this.validateApimOAuthServer(config); - await this.validateProduct(config); - await this.validateAppAad(config); - await this.validateClientAad(config); - console.log("[Successfully] validate apim provision."); - } - - public static async validateDeploy( - ctx: any, - projectPath: string, - apiPrefix: string, - apiVersion: string, - apiDocumentPath?: string, - versionSetId?: string, - apiPath?: string - ): Promise { - console.log("Start validate apim deploy."); - - const config = new Config(ctx); - chai.assert.isNotEmpty(config?.resourceNameSuffix); - chai.assert.equal(config?.apiPrefix, apiPrefix); - - chai.assert.equal(config?.apiDocumentPath, apiDocumentPath ?? "openapi/openapi.json"); - chai.assert.equal( - config?.versionSetId, - versionSetId ?? md5(`${apiPrefix}-${config?.resourceNameSuffix}`) - ); - chai.assert.equal(config?.apiPath, apiPath ?? `${apiPrefix}-${config?.resourceNameSuffix}`); - - await this.validateVersionSet(config); - await this.validateApi(config, projectPath, apiVersion); - await this.validateProductApi(config, apiVersion); - console.log("[Successfully] validate apim deploy."); - } - - private static getApimInfo(config: Config): { resourceGroup: string; serviceName: string } { - chai.assert.isNotEmpty(config?.apimResourceGroupName); - chai.assert.isNotEmpty(config?.serviceResourceId); - const serviceName = getApimServiceNameFromResourceId(config?.serviceResourceId as string); - - chai.assert.isNotEmpty(serviceName); - return { resourceGroup: config?.apimResourceGroupName, serviceName: serviceName! }; - } - - private static async loadOpenApiSpec(config: Config, projectPath: string): Promise { - chai.assert.isNotEmpty(config?.apiDocumentPath); - return await fs.readJson(`${projectPath}/${config?.apiDocumentPath}`); - } - - private static async validateApimService(config: Config): Promise { - const apim = this.getApimInfo(config); - console.log( - `validate apim service. Rg: ${apim.resourceGroup}, service name: ${apim.serviceName}` - ); - const apimManagementService = await this.apimClient?.apiManagementService.get( - apim.resourceGroup, - apim.serviceName - ); - chai.assert.isNotEmpty(apimManagementService); - chai.assert.equal(apimManagementService?.sku.name, "Consumption"); - } - - private static async validateApimOAuthServer(config: Config): Promise { - const apim = this.getApimInfo(config); - chai.assert.isNotEmpty(config?.authServerResourceId); - console.log( - `validate apim OAuth service. auth server resource id: ${config?.authServerResourceId}` - ); - - const oAuthServerId = getAuthServiceNameFromResourceId(config?.authServerResourceId as string); - chai.assert.isNotEmpty(oAuthServerId); - - const oAuthServer = await this.apimClient?.authorizationServer?.get( - apim.resourceGroup, - apim.serviceName, - oAuthServerId - ); - chai.assert.isNotEmpty(oAuthServer); - chai.assert.isNotEmpty(oAuthServer?.displayName); - - chai.assert.equal(oAuthServer?.clientId, config?.apimClientAADClientId); - - chai.assert.isNotEmpty(config?.applicationIdUris); - chai.assert.equal(oAuthServer?.defaultScope, `${config?.applicationIdUris}/.default`); - - chai.assert.isNotEmpty(config?.teamsAppTenantId); - chai.assert.equal( - oAuthServer?.authorizationEndpoint, - `https://login.microsoftonline.com/${config?.teamsAppTenantId}/oauth2/v2.0/authorize` - ); - chai.assert.equal( - oAuthServer?.tokenEndpoint, - `https://login.microsoftonline.com/${config?.teamsAppTenantId}/oauth2/v2.0/token` - ); - } - - private static async validateProduct(config: Config): Promise { - const apim = this.getApimInfo(config); - chai.assert.isNotEmpty(config?.productResourceId); - console.log(`validate apim product. auth product resource id: ${config?.productResourceId}`); - - const productId = getproductNameFromResourceId(config?.productResourceId as string); - - chai.assert.isNotEmpty(productId); - const product = await this.apimClient?.product?.get( - apim.resourceGroup, - apim.serviceName, - productId - ); - chai.assert.isNotEmpty(product); - chai.assert.isFalse(product?.subscriptionRequired); - } - - private static async validateVersionSet(config: Config): Promise { - console.log("Validate version set"); - const apim = this.getApimInfo(config); - chai.assert.isNotEmpty(config?.versionSetId); - const versionSet = await this.apimClient?.apiVersionSet?.get( - apim.resourceGroup, - apim.serviceName, - config?.versionSetId - ); - chai.assert.isNotEmpty(versionSet); - } - - private static async validateApi( - config: Config, - projectPath: string, - apiVersion: string - ): Promise { - console.log("Validate api"); - const apim = this.getApimInfo(config); - const spec = await this.loadOpenApiSpec(config, projectPath); - - chai.assert.isNotEmpty(config?.apiPrefix); - chai.assert.isNotEmpty(config?.resourceNameSuffix); - const api = await this.apimClient?.api?.get( - apim.resourceGroup, - apim.serviceName, - `${config?.apiPrefix}-${config?.resourceNameSuffix}-${apiVersion}` - ); - chai.assert.isNotEmpty(api); - chai.assert.equal(api?.path, `${config?.apiPrefix}-${config?.resourceNameSuffix}`); - - const oAuthServerId = getAuthServiceNameFromResourceId(config?.authServerResourceId as string); - chai.assert.isNotEmpty(oAuthServerId); - chai.assert.equal( - api?.authenticationSettings?.oAuth2?.authorizationServerId, - `${oAuthServerId}` - ); - - chai.assert.isNotEmpty(config?.versionSetId); - chai.assert.include(api?.apiVersionSetId, config?.versionSetId); - - chai.assert.isNotEmpty(config?.functionEndpoint); - chai.assert.equal(api?.serviceUrl, `${config?.functionEndpoint}/api`); - - chai.assert.equal(api?.displayName, spec.info.title); - chai.assert.equal(api?.apiVersion, apiVersion); - chai.assert.isFalse(api?.subscriptionRequired); - chai.assert.includeMembers(api?.protocols ?? [], ["https"]); - } - - private static async validateProductApi(config: Config, apiVersion: string): Promise { - console.log("Validate product api"); - const apim = this.getApimInfo(config); - chai.assert.isNotEmpty(config?.apiPrefix); - chai.assert.isNotEmpty(config?.resourceNameSuffix); - const productId = getproductNameFromResourceId(config?.productResourceId as string); - - chai.assert.isNotEmpty(productId); - - const productApi = await this.apimClient?.productApi.checkEntityExists( - apim.resourceGroup, - apim.serviceName, - productId, - `${config?.apiPrefix}-${config?.resourceNameSuffix}-${apiVersion}` - ); - chai.assert.isNotEmpty(productApi); - } - - private static async validateClientAad(config: Config): Promise { - console.log("Validate client aad"); - chai.assert.isNotEmpty(config?.apimClientAADObjectId); - const response = await retry( - async () => { - try { - return await this.axiosInstance?.get(`/applications/${config?.apimClientAADObjectId}`); - } catch (error) { - if (error?.response?.status == 404) { - return undefined; - } - throw error; - } - }, - (response) => { - return ( - !response || - response?.data?.passwordCredentials?.length == 0 || - response?.data?.requiredResourceAccess?.length === 0 - ); - } - ); - - const enableIdTokenIssuance = response?.data?.web.implicitGrantSettings?.enableIdTokenIssuance; - chai.assert.isTrue(enableIdTokenIssuance); - - const passwordCredentials = response?.data?.passwordCredentials as any[]; - chai.assert.isNotEmpty(passwordCredentials); - - const requiredResourceAccess = response?.data?.requiredResourceAccess as any[]; - chai.assert.isNotEmpty(requiredResourceAccess); - - chai.assert.isNotEmpty(config?.clientId); - chai.assert.include( - requiredResourceAccess.map((x) => x?.resourceAppId as string), - config?.clientId - ); - - chai.assert.isNotEmpty(config?.oauth2PermissionScopeId); - const resourceAccessObj = requiredResourceAccess.find( - (x) => x?.resourceAppId === config?.clientId - ); - chai.assert.deepInclude(resourceAccessObj.resourceAccess, { - id: config?.oauth2PermissionScopeId, - type: "Scope", - }); - } - - private static async validateAppAad(config: Config): Promise { - console.log("Validate aad app"); - chai.assert.isNotEmpty(config?.objectId); - chai.assert.isNotEmpty(config?.apimClientAADClientId); - - const aadResponse = await retry( - async () => { - try { - return await this.axiosInstance?.get(`/applications/${config?.objectId}`); - } catch (error) { - if (error?.response?.status == 404) { - return undefined; - } - throw error; - } - }, - (response) => { - return !response || response?.data?.api?.knownClientApplications.length === 0; - } - ); - const knownClientApplications = aadResponse?.data?.api?.knownClientApplications as string[]; - chai.assert.isNotEmpty(knownClientApplications); - chai.assert.include(knownClientApplications, config?.apimClientAADClientId); - - chai.assert.isNotEmpty(config?.clientId); - const servicePrincipalResponse = await retry( - async () => { - return await this.axiosInstance?.get( - `/servicePrincipals?$filter=appId eq '${config?.clientId}'` - ); - }, - (response) => { - return !response || response?.data?.value.length === 0; - } - ); - const servicePrincipals = servicePrincipalResponse?.data?.value as any[]; - chai.assert.isNotEmpty(servicePrincipals); - chai.assert.include( - servicePrincipals.map((sp) => sp.appId as string), - config?.clientId - ); - } -} - -class Config { - private readonly config: any; - - constructor(config: any) { - this.config = config; - } - - get functionEndpoint() { - return this.config[PluginId.Function][StateConfigKey.functionEndpoint]; - } - - get objectId() { - return this.config[PluginId.Aad][StateConfigKey.objectId]; - } - get clientId() { - return this.config[PluginId.Aad][StateConfigKey.clientId]; - } - get oauth2PermissionScopeId() { - return this.config[PluginId.Aad][StateConfigKey.oauth2PermissionScopeId]; - } - get applicationIdUris() { - return this.config[PluginId.Aad][StateConfigKey.applicationIdUris]; - } - - get subscriptionId() { - return this.config[PluginId.Solution][StateConfigKey.subscriptionId]; - } - get resourceNameSuffix() { - return this.config[PluginId.Solution][StateConfigKey.resourceNameSuffix]; - } - get teamsAppTenantId() { - return this.config[PluginId.Solution][StateConfigKey.teamsAppTenantId]; - } - get resourceGroupName() { - return this.config[PluginId.Solution][StateConfigKey.resourceGroupName]; - } - get location() { - return this.config[PluginId.Solution][StateConfigKey.location]; - } - - get apimResourceGroupName() { - return getResourceGroupNameFromResourceId( - this.config[PluginId.Apim][StateConfigKey.serviceResourceId] - ); - } - get apimClientAADObjectId() { - return this.config[PluginId.Apim][StateConfigKey.apimClientAADObjectId]; - } - get apimClientAADClientId() { - return this.config[PluginId.Apim][StateConfigKey.apimClientAADClientId]; - } - get apimClientAADClientSecret() { - return this.config[PluginId.Apim][StateConfigKey.apimClientAADClientSecret]; - } - get apiPrefix() { - return this.config[PluginId.Apim][StateConfigKey.apiPrefix]; - } - get versionSetId() { - return this.config[PluginId.Apim][StateConfigKey.versionSetId]; - } - get apiPath() { - return this.config[PluginId.Apim][StateConfigKey.apiPath]; - } - get apiDocumentPath() { - return this.config[PluginId.Apim][StateConfigKey.apiDocumentPath]; - } - get serviceResourceId() { - return this.config[PluginId.Apim][StateConfigKey.serviceResourceId]; - } - get productResourceId() { - return this.config[PluginId.Apim][StateConfigKey.productResourceId]; - } - get authServerResourceId() { - return this.config[PluginId.Apim][StateConfigKey.authServerResourceId]; - } -} - -async function retry( - fn: (retries: number) => Promise, - condition: (result: T) => boolean, - maxRetries = 20, - retryTimeInterval = 1000 -): Promise { - let executionIndex = 1; - let result: T = await fn(executionIndex); - while (executionIndex <= maxRetries && condition(result)) { - await delay(executionIndex * retryTimeInterval); - result = await fn(executionIndex); - ++executionIndex; - } - return result; -} - -function delay(ms: number): Promise { - if (ms <= 0) { - return Promise.resolve(); - } - - // tslint:disable-next-line no-string-based-set-timeout - return new Promise((resolve) => setTimeout(resolve, ms)); -} diff --git a/packages/cli/tests/commonlib/constants.ts b/packages/cli/tests/commonlib/constants.ts index 074f906d28..0a405e980b 100644 --- a/packages/cli/tests/commonlib/constants.ts +++ b/packages/cli/tests/commonlib/constants.ts @@ -138,17 +138,6 @@ export class StateConfigKey { static readonly identityClientId = "identityClientId"; // key vault static readonly keyVaultResourceId = "keyVaultResourceId"; - // Apim - static readonly serviceResourceId = "serviceResourceId"; - static readonly productResourceId = "productResourceId"; - static readonly authServerResourceId = "authServerResourceId"; - static readonly apiPrefix = "apiPrefix"; - static readonly versionSetId = "versionSetId"; - static readonly apiPath = "apiPath"; - static readonly apiDocumentPath = "apiDocumentPath"; - static readonly apimClientAADObjectId = "apimClientAADObjectId"; - static readonly apimClientAADClientId = "apimClientAADClientId"; - static readonly apimClientAADClientSecret = "apimClientAADClientSecret"; static readonly skuName = "skuName"; } diff --git a/packages/cli/tests/commonlib/index.ts b/packages/cli/tests/commonlib/index.ts index 7398fbaa22..33d5c9f681 100644 --- a/packages/cli/tests/commonlib/index.ts +++ b/packages/cli/tests/commonlib/index.ts @@ -9,7 +9,6 @@ export * from "./simpleAuthValidator"; export * from "./sqlValidator"; export * from "./aadManager"; export * from "./resourceGroupManager"; -export * from "./apimValidator"; export * from "./functionValidator"; export * from "./botValidator"; export * from "./frontendValidator"; diff --git a/packages/cli/tests/commonlib/utilities.ts b/packages/cli/tests/commonlib/utilities.ts index ad6ac2360a..e10c811632 100644 --- a/packages/cli/tests/commonlib/utilities.ts +++ b/packages/cli/tests/commonlib/utilities.ts @@ -46,33 +46,6 @@ export function getKeyVaultNameFromResourceId(keyVaultResourceId: string): strin return result; } -export function getApimServiceNameFromResourceId(resourceId: string): string { - const result = parseFromResourceId( - /providers\/Microsoft.ApiManagement\/service\/([^\/]*)/i, - resourceId - ); - if (!result) { - throw new Error(failedToParseResourceIdErrorMessage("apim service name", resourceId)); - } - return result; -} - -export function getproductNameFromResourceId(resourceId: string): string { - const result = parseFromResourceId(/products\/([^\/]*)/i, resourceId); - if (!result) { - throw new Error(failedToParseResourceIdErrorMessage("product name", resourceId)); - } - return result; -} - -export function getAuthServiceNameFromResourceId(resourceId: string): string { - const result = parseFromResourceId(/authorizationServers\/([^\/]*)/i, resourceId); - if (!result) { - throw new Error(failedToParseResourceIdErrorMessage("auth service name", resourceId)); - } - return result; -} - export function parseFromResourceId(pattern: RegExp, resourceId: string): string { const result = resourceId.match(pattern); return result ? result[1].trim() : ""; diff --git a/packages/fx-core/.nycrc b/packages/fx-core/.nycrc index 858619fa07..491f54553c 100644 --- a/packages/fx-core/.nycrc +++ b/packages/fx-core/.nycrc @@ -11,7 +11,6 @@ "src/common/armInterface.ts", "src/plugins/resource/localdebug/legacyPlugin.ts", "src/plugins/resource/aad/interfaces/**/*", - "src/component/resource/apim/interfaces/**/*", "src/plugins/resource/bot/interface.ts", "src/plugins/resource/bot/appStudio/interfaces/**/*", "src/plugins/resource/frontend/interface.ts", diff --git a/packages/fx-core/src/component/constants.ts b/packages/fx-core/src/component/constants.ts index 4382256b22..f485d9a2d5 100644 --- a/packages/fx-core/src/component/constants.ts +++ b/packages/fx-core/src/component/constants.ts @@ -19,7 +19,6 @@ export const ComponentNames = { SPFxTab: "spfx-tab", SPFx: "spfx", Identity: "identity", - APIMFeature: "apim-feature", APIM: "apim", KeyVault: "key-vault", AzureSQL: "azure-sql", @@ -92,29 +91,6 @@ export const ErrorConstants = { unhandledErrorMessage: "Unhandled Error", }; -export const APIMOutputs = { - serviceResourceId: { - key: "serviceResourceId", - bicepVariable: "provisionOutputs.apimOutput.value.serviceResourceId", - }, - productResourceId: { - key: "productResourceId", - bicepVariable: "provisionOutputs.apimOutput.value.productResourceId", - }, - authServerResourceId: { - key: "authServerResourceId", - }, - apimClientAADObjectId: { - key: "apimClientAADObjectId", - }, - apimClientAADClientId: { - key: "apimClientAADClientId", - }, - apimClientAADClientSecret: { - key: "apimClientAADClientSecret", - }, -}; - export const AadAppOutputs = { applicationIdUris: { key: "applicationIdUris", diff --git a/packages/fx-core/src/component/registerService.ts b/packages/fx-core/src/component/registerService.ts index cc637e660c..b528d7ef4f 100644 --- a/packages/fx-core/src/component/registerService.ts +++ b/packages/fx-core/src/component/registerService.ts @@ -7,7 +7,6 @@ import "./code/botCode"; import "./code/tab/tabCode"; import "./feature/cicd/cicd"; import "./feature/sso"; -import "./resource/apim/apim"; import "./resource/azureAppService/azureFunction"; import "./resource/azureAppService/azureWebApp"; import "./resource/azureStorage/azureStorage"; diff --git a/packages/fx-core/templates/plugins/resource/apim/README.md b/packages/fx-core/templates/plugins/resource/apim/README.md deleted file mode 100644 index 8f139fde72..0000000000 --- a/packages/fx-core/templates/plugins/resource/apim/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Publish Teams Backend (Azure Functions) to Azure API Management - -Azure API Management (APIM) is used to create consistent and modern API gateways for existing backend services. With Teams Toolkit or TeamsFx CLI, you can easily publish your backend APIs (Azure Functions) to APIM instance. - -## Prerequisite - -- [Node.js](https://nodejs.org/), supported versions: 14, 16, 18 (preview) -- An Microsoft 365 account. If you do not have an Microsoft 365 account, apply for one from [Microsoft 365 developer program](https://developer.microsoft.com/en-us/microsoft-365/dev-program) -- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/en-us/free/) - - Ensure the resource provider 'Microsoft.ApiManagement' is registered for the subscription by following [document](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/error-register-resource-provider#solution-3---azure-portal) -- Teams Toolkit Extension for Visual Studio Code or TeamsFx CLI - -## Enable API Management Feature in TeamsFx - -> Publish APIs to APIM requires Azure Functions in your project. If your project does not include Azure Functions, please note that we will automatically add one for you. Read about [Azure Functions in TeamsFx](https://github.com/OfficeDev/TeamsFx/tree/main/templates/function-base/js/default#readme) to learn more. - -You can enable Azure API Management by following steps: -| Using Teams Toolkit| Using TeamsFx CLI| -| :------------------| :----------------| -| Open command palette, select `Teams: Add Resources` and select `Register APIs in Azure API Management` in next step.| Run command `teamsfx resource add azure-apim`.| - -## Deploy to Azure - -Deploy your project to Azure by following these steps: - -| From Visual Studio Code | From TeamsFx CLI | -| :-----------------------| :----------------| -|
  • Open Teams Toolkit, and sign into Azure by clicking the `Sign in to Azure` under the `ACCOUNTS` section from sidebar.\*
  • After you signed in, select a subscription under your account.\*
  • Open Teams Toolkit, and sign into Microsoft 365 by clicking the `Sign in to Microsoft 365` under the `ACCOUNTS` section from sidebar.\*
  • Open the command palette and select: `Teams: Provision in the cloud`.
  • Open the command palette and select: `Teams: Deploy to the cloud`.
|
  • Run command `teamsfx account login azure`.\*
  • Run command `teamsfx account set --subscription $subscriptionId`.\*
  • Run command `teamsfx account login m365`.\*
  • Run command `teamsfx provision`.
  • First-time: Run command `teamsfx deploy function apim --open-api-document openapi/openapi.json --api-prefix $apiPrefix --api-version $apiVersion`.
  • Non-first-time: Run command `teamsfx deploy function apim --api-version $apiVersion`.
| - -> \* Skip this step if you have already done in the previous steps. - -In the deployment step, there will be some inputs needed: - -- Select the resource `API Management`. The resource `Azure Function` should also be selected if the API changes have never been deployed to the cloud. -- The OpenAPI Specification File (Default: `openapi/openapi.json`). -- Input API prefix. The API path will be `$apiPrefix-$resourceSuffix`. The API Name will be `$apiPrefix-$resourceSuffix-$apiVersion`. -- Select an existing API version or input a new API version. - -> Note: Provisioning and deployment may incur charges to your Azure Subscription. - -## Write OpenAPI Document - -We support both yaml and json format for the OpenAPI document. You need to follow the [OpenAPI Specification](https://swagger.io/resources/open-api/), author the OpenAPI document and ensure the API schema is aligned with the Azure Functions HTTP trigger implementation. - -Below is a sample swagger file for the default HTTP trigger function. You can copy the content into `./openapi/openapi.json`, and change the content according to your modification (E.g. `/getUserProfile` -> `/$yourFunctionName` ). - -```json -{ - "openapi": "3.0.1", - "info": { - "title": "{appName}", - "version": "v1" - }, - "paths": { - "/getUserProfile": { - "get": { - "summary": "Get User Profile", - "operationId": "get-user-profile", - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "receivedHTTPRequestBody": { - "type": "string" - }, - "userInfoMessage": { - "type": "string" - }, - "graphClientMessage": { - "type": "object" - } - } - } - } - } - } - } - } - } - } -} -``` - -You can use your favorite tool to generate an OpenAPI document, such as [OpenAPI (Swagger) Editor](https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi) and [swagger-jsdoc](https://github.com/Surnet/swagger-jsdoc/). - -## Documentation - -Find help in [troubleshooting guide](https://aka.ms/teamsfx-apim-help) if you met any issues.