From c935b2ca0e3b2e390459fc4a4851d93e32a8469b Mon Sep 17 00:00:00 2001 From: Jun Han Date: Fri, 7 Jun 2024 16:12:53 +0800 Subject: [PATCH 1/2] feat: upgrade Azure Functions to v4 for graph-connector-app --- .../api/{data => }/assets/ApplianceParts.csv | 0 .../api/connection/function.json | 25 ----------- graph-connector-app/api/data/function.json | 25 ----------- graph-connector-app/api/extensions.csproj | 11 ----- graph-connector-app/api/host.json | 6 ++- graph-connector-app/api/package.json | 7 ++-- graph-connector-app/api/proxies.json | 4 -- graph-connector-app/api/schema/function.json | 25 ----------- graph-connector-app/api/{ => src}/config.ts | 0 .../index.ts => src/functions/connection.ts} | 41 +++++++++---------- .../{data/index.ts => src/functions/data.ts} | 39 ++++++++---------- .../index.ts => src/functions/schema.ts} | 41 +++++++++---------- .../index.ts => src/functions/status.ts} | 41 +++++++++---------- graph-connector-app/api/status/function.json | 25 ----------- graph-connector-app/teamsapp.local.yml | 11 +---- graph-connector-app/teamsapp.yml | 13 ------ 16 files changed, 85 insertions(+), 229 deletions(-) rename graph-connector-app/api/{data => }/assets/ApplianceParts.csv (100%) delete mode 100644 graph-connector-app/api/connection/function.json delete mode 100644 graph-connector-app/api/data/function.json delete mode 100644 graph-connector-app/api/extensions.csproj delete mode 100644 graph-connector-app/api/proxies.json delete mode 100644 graph-connector-app/api/schema/function.json rename graph-connector-app/api/{ => src}/config.ts (100%) rename graph-connector-app/api/{connection/index.ts => src/functions/connection.ts} (77%) rename graph-connector-app/api/{data/index.ts => src/functions/data.ts} (81%) rename graph-connector-app/api/{schema/index.ts => src/functions/schema.ts} (83%) rename graph-connector-app/api/{status/index.ts => src/functions/status.ts} (76%) delete mode 100644 graph-connector-app/api/status/function.json diff --git a/graph-connector-app/api/data/assets/ApplianceParts.csv b/graph-connector-app/api/assets/ApplianceParts.csv similarity index 100% rename from graph-connector-app/api/data/assets/ApplianceParts.csv rename to graph-connector-app/api/assets/ApplianceParts.csv diff --git a/graph-connector-app/api/connection/function.json b/graph-connector-app/api/connection/function.json deleted file mode 100644 index 1aa4cf95..00000000 --- a/graph-connector-app/api/connection/function.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "post" - ], - "route": "connection" - }, - { - "type": "http", - "direction": "out", - "name": "$return" - }, - { - "direction": "in", - "name": "teamsfxContext", - "type": "TeamsFx" - } - ], - "scriptFile": "../dist/connection/index.js" -} \ No newline at end of file diff --git a/graph-connector-app/api/data/function.json b/graph-connector-app/api/data/function.json deleted file mode 100644 index 0bd39737..00000000 --- a/graph-connector-app/api/data/function.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "post" - ], - "route": "data" - }, - { - "type": "http", - "direction": "out", - "name": "$return" - }, - { - "direction": "in", - "name": "teamsfxContext", - "type": "TeamsFx" - } - ], - "scriptFile": "../dist/data/index.js" -} \ No newline at end of file diff --git a/graph-connector-app/api/extensions.csproj b/graph-connector-app/api/extensions.csproj deleted file mode 100644 index 299e3587..00000000 --- a/graph-connector-app/api/extensions.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - netcoreapp3.1 - - ** - - - - - - \ No newline at end of file diff --git a/graph-connector-app/api/host.json b/graph-connector-app/api/host.json index 369b5be8..9df91361 100644 --- a/graph-connector-app/api/host.json +++ b/graph-connector-app/api/host.json @@ -7,5 +7,9 @@ "excludedTypes": "Request" } } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" } -} +} \ No newline at end of file diff --git a/graph-connector-app/api/package.json b/graph-connector-app/api/package.json index 2272480c..7694ba28 100644 --- a/graph-connector-app/api/package.json +++ b/graph-connector-app/api/package.json @@ -2,7 +2,7 @@ "name": "teamsfx-template-api", "version": "1.0.0", "engines": { - "node": "16 || 18" + "node": "18" }, "scripts": { "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", @@ -14,7 +14,7 @@ "start": "npx func start" }, "dependencies": { - "@azure/functions": "^1.2.2", + "@azure/functions": "^4.2.0", "@microsoft/microsoft-graph-client": "^3.0.0", "@microsoft/teamsfx": "^2.0.0", "csv-parse": "^5.0.4", @@ -24,5 +24,6 @@ "@types/node": "^18.7.18", "env-cmd": "^10.1.0", "typescript": "^4.4.4" - } + }, + "main": "dist/src/functions/*.js" } \ No newline at end of file diff --git a/graph-connector-app/api/proxies.json b/graph-connector-app/api/proxies.json deleted file mode 100644 index b385252f..00000000 --- a/graph-connector-app/api/proxies.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/proxies", - "proxies": {} -} diff --git a/graph-connector-app/api/schema/function.json b/graph-connector-app/api/schema/function.json deleted file mode 100644 index 88b45310..00000000 --- a/graph-connector-app/api/schema/function.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "post" - ], - "route": "schema" - }, - { - "type": "http", - "direction": "out", - "name": "$return" - }, - { - "direction": "in", - "name": "teamsfxContext", - "type": "TeamsFx" - } - ], - "scriptFile": "../dist/schema/index.js" -} \ No newline at end of file diff --git a/graph-connector-app/api/config.ts b/graph-connector-app/api/src/config.ts similarity index 100% rename from graph-connector-app/api/config.ts rename to graph-connector-app/api/src/config.ts diff --git a/graph-connector-app/api/connection/index.ts b/graph-connector-app/api/src/functions/connection.ts similarity index 77% rename from graph-connector-app/api/connection/index.ts rename to graph-connector-app/api/src/functions/connection.ts index 00e94355..8ad7eb83 100644 --- a/graph-connector-app/api/connection/index.ts +++ b/graph-connector-app/api/src/functions/connection.ts @@ -1,16 +1,11 @@ // Import polyfills for fetch required by msgraph-sdk-javascript. import "isomorphic-fetch"; -import { Context, HttpRequest } from "@azure/functions"; +import { app, InvocationContext, HttpRequest, HttpResponseInit } from "@azure/functions"; import { Client } from "@microsoft/microsoft-graph-client"; import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials"; import { AppCredential, AppCredentialAuthConfig } from "@microsoft/teamsfx"; import config from "../config"; -interface Response { - status: number; - body: { [key: string]: any }; -} - const authConfig: AppCredentialAuthConfig = { authorityHost: config.authorityHost, clientId: config.clientId, @@ -18,26 +13,22 @@ const authConfig: AppCredentialAuthConfig = { clientSecret: config.clientSecret, }; -type TeamsfxContext = { [key: string]: any }; - /** - * @param {Context} context - The Azure Functions context object. * @param {HttpRequest} req - The HTTP request. - * @param {teamsfxContext} TeamsfxContext - The context generated by teamsfx binding. + * @param {InvocationContext} context - The Azure Functions context object. */ -export default async function run( - context: Context, +export async function connection( req: HttpRequest, - teamsfxContext: TeamsfxContext -): Promise { + context: InvocationContext +): Promise { context.log("HTTP trigger function processed a request."); - const connectionId = req.query.connectionId; + const connectionId = req.query.get("connectionId"); // Initialize response. - const res: Response = { + const res: HttpResponseInit = { status: 200, - body: { + jsonBody: { connectionAlreadyExists: false, }, }; @@ -46,10 +37,10 @@ export default async function run( try { appCredential = new AppCredential(authConfig); } catch (e) { - context.log.error(e); + context.error(e); return { status: 500, - body: { + jsonBody: { error: "Failed to construct TeamsFx with Application Identity. " + "Ensure your function app is configured with the right Azure AD App registration.", @@ -76,9 +67,9 @@ export default async function run( }); } catch (e) { if (e?.statusCode === 409) { - res.body.connectionAlreadyExists = true; + res.jsonBody.connectionAlreadyExists = true; } else { - context.log.error(e); + context.error(e); let error = "Failed to create a connection for Graph connector: " + e.toString(); if (e?.statusCode === 401) { @@ -87,7 +78,7 @@ export default async function run( } return { status: e?.statusCode ?? 500, - body: { + jsonBody: { error, }, }; @@ -96,3 +87,9 @@ export default async function run( return res; } + +app.http("connection", { + methods: ["POST"], + authLevel: "anonymous", + handler: connection, +}); \ No newline at end of file diff --git a/graph-connector-app/api/data/index.ts b/graph-connector-app/api/src/functions/data.ts similarity index 81% rename from graph-connector-app/api/data/index.ts rename to graph-connector-app/api/src/functions/data.ts index 5d0925ec..ab18f4ee 100644 --- a/graph-connector-app/api/data/index.ts +++ b/graph-connector-app/api/src/functions/data.ts @@ -1,6 +1,6 @@ // Import polyfills for fetch required by msgraph-sdk-javascript. import "isomorphic-fetch"; -import { Context, HttpRequest } from "@azure/functions"; +import { app, InvocationContext, HttpRequest, HttpResponseInit } from "@azure/functions"; import { Client } from "@microsoft/microsoft-graph-client"; import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials"; import { AppCredential, AppCredentialAuthConfig } from "@microsoft/teamsfx"; @@ -9,10 +9,6 @@ import * as path from "path"; import { parse } from "csv-parse/sync"; import config from "../config"; -interface Response { - status: number; - body: { [key: string]: any }; -} const authConfig: AppCredentialAuthConfig = { authorityHost: config.authorityHost, @@ -21,36 +17,32 @@ const authConfig: AppCredentialAuthConfig = { clientSecret: config.clientSecret, }; -type TeamsfxContext = { [key: string]: any }; - /** - * @param {Context} context - The Azure Functions context object. * @param {HttpRequest} req - The HTTP request. - * @param {teamsfxContext} TeamsfxContext - The context generated by teamsfx binding. + * @param {InvocationContext} context - The Azure Functions context object. */ -export default async function run( - context: Context, +export async function data( req: HttpRequest, - teamsfxContext: TeamsfxContext -): Promise { + context: InvocationContext +): Promise { context.log("HTTP trigger function processed a request."); - const connectionId = req.query.connectionId; + const connectionId = req.query.get("connectionId"); // Initialize response. - const res: Response = { + const res: HttpResponseInit = { status: 200, - body: {}, + jsonBody: {}, }; let appCredential; try { appCredential = new AppCredential(authConfig); } catch (e) { - context.log.error(e); + context.error(e); return { status: 500, - body: { + jsonBody: { error: "Failed to construct AppCredential with Application Identity. " + "Ensure your function app is configured with the right Azure AD App registration.", @@ -63,7 +55,6 @@ export default async function run( const csvFileContent = ( await readFile( path.join( - context.executionContext.functionDirectory, "assets", "ApplianceParts.csv" ) @@ -110,10 +101,10 @@ export default async function run( }); } } catch (e) { - context.log.error(e); + context.error(e); return { status: e?.statusCode ?? 500, - body: { + jsonBody: { error: "Failed to ingest items: " + e.toString(), }, }; @@ -121,3 +112,9 @@ export default async function run( return res; } + +app.http("data", { + methods: ["POST"], + authLevel: "anonymous", + handler: data, +}); \ No newline at end of file diff --git a/graph-connector-app/api/schema/index.ts b/graph-connector-app/api/src/functions/schema.ts similarity index 83% rename from graph-connector-app/api/schema/index.ts rename to graph-connector-app/api/src/functions/schema.ts index 7b464def..727a62cf 100644 --- a/graph-connector-app/api/schema/index.ts +++ b/graph-connector-app/api/src/functions/schema.ts @@ -1,16 +1,11 @@ // Import polyfills for fetch required by msgraph-sdk-javascript. import "isomorphic-fetch"; -import { Context, HttpRequest } from "@azure/functions"; +import { app, InvocationContext, HttpRequest, HttpResponseInit } from "@azure/functions"; import { Client, ResponseType } from "@microsoft/microsoft-graph-client"; import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials"; import { AppCredential, AppCredentialAuthConfig } from "@microsoft/teamsfx"; import config from "../config"; -interface Response { - status: number; - body: { [key: string]: any }; -} - const authConfig: AppCredentialAuthConfig = { authorityHost: config.authorityHost, clientId: config.clientId, @@ -18,36 +13,32 @@ const authConfig: AppCredentialAuthConfig = { clientSecret: config.clientSecret, }; -type TeamsfxContext = { [key: string]: any }; - /** - * @param {Context} context - The Azure Functions context object. * @param {HttpRequest} req - The HTTP request. - * @param {teamsfxContext} TeamsfxContext - The context generated by teamsfx binding. + * @param {InvocationContext} context - The Azure Functions context object. */ -export default async function run( - context: Context, +export async function schema( req: HttpRequest, - teamsfxContext: TeamsfxContext -): Promise { + context: InvocationContext +): Promise { context.log("HTTP trigger function processed a request."); - const connectionId = req.query.connectionId; + const connectionId = req.query.get("connectionId"); // Initialize response. - const res: Response = { + const res: HttpResponseInit = { status: 200, - body: {}, + jsonBody: {}, }; let appCredential; try { appCredential = new AppCredential(authConfig); } catch (e) { - context.log.error(e); + context.error(e); return { status: 500, - body: { + jsonBody: { error: "Failed to construct AppCredential with Application Identity. " + "Ensure your function app is configured with the right Azure AD App registration.", @@ -129,12 +120,12 @@ export default async function run( }, ], }); - res.body.location = result.headers.get("Location"); + res.jsonBody.location = result.headers.get("Location"); } catch (e) { - context.log.error(e); + context.error(e); return { status: e?.statusCode ?? 500, - body: { + jsonBody: { error: "Failed to register a schema for connection: " + e.toString(), }, }; @@ -142,3 +133,9 @@ export default async function run( return res; } + +app.http("schema", { + methods: ["POST"], + authLevel: "anonymous", + handler: schema, +}); \ No newline at end of file diff --git a/graph-connector-app/api/status/index.ts b/graph-connector-app/api/src/functions/status.ts similarity index 76% rename from graph-connector-app/api/status/index.ts rename to graph-connector-app/api/src/functions/status.ts index 50ba08f2..1601e11d 100644 --- a/graph-connector-app/api/status/index.ts +++ b/graph-connector-app/api/src/functions/status.ts @@ -5,19 +5,12 @@ // Import polyfills for fetch required by msgraph-sdk-javascript. import "isomorphic-fetch"; -import { Context, HttpRequest } from "@azure/functions"; +import { app, InvocationContext, HttpRequest, HttpResponseInit } from "@azure/functions"; import { Client } from "@microsoft/microsoft-graph-client"; import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials"; import { AppCredential, AppCredentialAuthConfig } from "@microsoft/teamsfx"; import config from "../config"; -interface Response { - status: number; - body: { [key: string]: any }; -} - -type TeamsfxContext = { [key: string]: any }; - const authConfig: AppCredentialAuthConfig = { authorityHost: config.authorityHost, clientId: config.clientId, @@ -26,31 +19,29 @@ const authConfig: AppCredentialAuthConfig = { }; /** - * @param {Context} context - The Azure Functions context object. * @param {HttpRequest} req - The HTTP request. - * @param {teamsfxContext} TeamsfxContext - The context generated by teamsfx binding. + * @param {InvocationContext} context - The Azure Functions context object. */ -export default async function run( - context: Context, +export async function status( req: HttpRequest, - teamsfxContext: TeamsfxContext -): Promise { + context: InvocationContext +): Promise { context.log("HTTP trigger function processed a request."); // Initialize response. - const res: Response = { + const res: HttpResponseInit = { status: 200, - body: {}, + jsonBody: {}, }; let appCredential; try { appCredential = new AppCredential(authConfig); } catch (e) { - context.log.error(e); + context.error(e); return { status: 500, - body: { + jsonBody: { error: "Failed to construct AppCredential with Application Identity. " + "Ensure your function app is configured with the right Azure AD App registration.", @@ -70,14 +61,14 @@ export default async function run( let graphClient: Client = Client.initWithMiddleware({ authProvider: authProvider, }); - const location = req.query.location; + const location = req.query.get("location"); const result = await graphClient.api(location).get(); - res.body.status = result.status; + res.jsonBody.status = result.status; } catch (e) { - context.log.error(e); + context.error(e); return { status: e?.statusCode ?? 500, - body: { + jsonBody: { error: "Failed to check connection schema status: " + e.toString(), }, }; @@ -85,3 +76,9 @@ export default async function run( return res; } + +app.http("status", { + methods: ["GET"], + authLevel: "anonymous", + handler: status, +}); \ No newline at end of file diff --git a/graph-connector-app/api/status/function.json b/graph-connector-app/api/status/function.json deleted file mode 100644 index e614f36d..00000000 --- a/graph-connector-app/api/status/function.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get" - ], - "route": "status" - }, - { - "type": "http", - "direction": "out", - "name": "$return" - }, - { - "direction": "in", - "name": "teamsfxContext", - "type": "TeamsFx" - } - ], - "scriptFile": "../dist/status/index.js" -} \ No newline at end of file diff --git a/graph-connector-app/teamsapp.local.yml b/graph-connector-app/teamsapp.local.yml index c79e98c6..f94728f3 100644 --- a/graph-connector-app/teamsapp.local.yml +++ b/graph-connector-app/teamsapp.local.yml @@ -66,16 +66,14 @@ deploy: devCert: trust: true func: - version: ~4.0.5174 + version: ~4.0.5455 symlinkDir: ./devTools/func - dotnet: true # Write the information of installed development tool(s) into environment # file for the specified environment variable(s). writeToEnvironmentFile: sslCertFile: SSL_CRT_FILE sslKeyFile: SSL_KEY_FILE funcPath: FUNC_PATH - dotnetPath: DOTNET_PATH # Run npm command - uses: cli/runNpmCommand @@ -89,13 +87,6 @@ deploy: args: install --no-audit workingDirectory: api - # TeamsFx Azure Functions project depends on extra Azure Functions binding extensions for HTTP trigger authorization. - - uses: cli/runDotnetCommand - with: - workingDirectory: api - args: build extensions.csproj -o bin --ignore-failed-sources - execPath: ${{DOTNET_PATH}} - # Generate runtime environment variables for tab - uses: file/createOrUpdateEnvironmentFile with: diff --git a/graph-connector-app/teamsapp.yml b/graph-connector-app/teamsapp.yml index 00cdb43a..e933a171 100644 --- a/graph-connector-app/teamsapp.yml +++ b/graph-connector-app/teamsapp.yml @@ -71,14 +71,6 @@ provision: appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip # Relative path to teamsfx folder. This is the path for built zip file. deploy: - # Install development tool(s) - - uses: devTool/install - with: - dotnet: true - # Write the information of installed development tool(s) into environment - # file for the specified environment variable(s). - writeToEnvironmentFile: - dotnetPath: DOTNET_PATH # Run npm command - uses: cli/runNpmCommand with: @@ -112,11 +104,6 @@ deploy: with: workingDirectory: api args: run build --if-present - - uses: cli/runDotnetCommand - with: - workingDirectory: api - args: build extensions.csproj -o bin --ignore-failed-sources - execPath: ${{DOTNET_PATH}} # Deploy your application to Azure Functions using the zip deploy feature. # For additional details, see at https://aka.ms/zip-deploy-to-azure-functions - uses: azureFunctions/zipDeploy From e64d757e588f9097b7ffe5b3a63b75886b4c557f Mon Sep 17 00:00:00 2001 From: Jun Han Date: Fri, 7 Jun 2024 16:16:14 +0800 Subject: [PATCH 2/2] feat: add local.settings.json --- graph-connector-app/api/local.settings.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 graph-connector-app/api/local.settings.json diff --git a/graph-connector-app/api/local.settings.json b/graph-connector-app/api/local.settings.json new file mode 100644 index 00000000..7e3601ca --- /dev/null +++ b/graph-connector-app/api/local.settings.json @@ -0,0 +1,6 @@ +{ + "IsEncrypted": false, + "Values": { + "FUNCTIONS_WORKER_RUNTIME": "node" + } +} \ No newline at end of file