-
Notifications
You must be signed in to change notification settings - Fork 3
Implement Open API on Azure Functions App
- Azure Subscription (Dev Essentials or existing subscription)
- .NET Core 2.1 SDK
- Azure CLI
- Azure Functions CLI
- Visual Studio Code
- Visual Studio Code Extensions:
Create an Azure Functions App instance from Azure Portal. Note the name of Azure Functions App instance for future use.
Once it's created, add app settings value like:
-
KeyVaultName
: "gib2019kv", -
OpenApi__Info__Version
: "2.0.0", -
OpenApi__Info__Title
: "GIB2019 Key Vault API", -
OpenApi__Info__Description
: "A Key Vault API that runs on Azure Functions 2.x for GIB2019.", -
OpenApi__Info__TermsOfService
: "https://github.com/fairdincom/GIB2019-LogicApps-KeyVault-Demo", -
OpenApi__Info__Contact__Name
: "fairdin.com", -
OpenApi__Info__Contact__Email
: "[email protected]", -
OpenApi__Info__Contact__Url
: "https://github.com/fairdincom/GIB2019-LogicApps-KeyVault-Demo/issues", -
OpenApi__Info__License__Name
: "MIT", -
OpenApi__Info__License__Url
: "http://opensource.org/licenses/MIT", -
OpenApi__ApiKey
: ""
Activate Managed Identity.
Add the Function App instance to the access policy of the Azure Key Vault instance.
In order to start from the baseline, run the following command.
git checkout step-03
cd KeyVault.FunctionApp
A bootstrap HTTP trigger has been created.
Run the following command to add NuGet package.
dotnet add . package Aliencube.AzureFunctions.Extensions.OpenApi -v 1.4.2
NuGet package has been added.
Update local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"KeyVaultName": "gib2019kv",
"OpenApi__Info__Version": "2.0.0",
"OpenApi__Info__Title": "GIB2019 Key Vault API",
"OpenApi__Info__Description": "A Key Vault API that runs on Azure Functions 2.x for GIB2019.",
"OpenApi__Info__TermsOfService": "https://github.com/fairdincom/GIB2019-LogicApps-KeyVault-Demo",
"OpenApi__Info__Contact__Name": "fairdin.com",
"OpenApi__Info__Contact__Email": "[email protected]",
"OpenApi__Info__Contact__Url": "https://github.com/fairdincom/GIB2019-LogicApps-KeyVault-Demo/issues",
"OpenApi__Info__License__Name": "MIT",
"OpenApi__Info__License__Url": "http://opensource.org/licenses/MIT",
"OpenApi__ApiKey": ""
}
}
-
Create HTTP response models on
SecretResponseModel.cs
.namespace KeyVault.FunctionApp { public class SecretResponseModel { public virtual string Name { get; set; } public virtual string Value { get; set; } } }
-
Create HTTP response models on
SecretCollectionResponseModel.cs
.using System.Collections.Generic; namespace KeyVault.FunctionApp { public class SecretCollectionResponseModel { public SecretCollectionResponseModel() { this.Items = new List<SecretResponseModel>(); } public virtual List<SecretResponseModel> Items { get; set; } } }
Create AppSettings.cs
using Aliencube.AzureFunctions.Extensions.OpenApi.Configurations;
namespace KeyVault.FunctionApp
{
public class AppSettings : OpenApiAppSettingsBase
{
public AppSettings() : base()
{
}
}
}
-
Open the
SecretsHttpTrigger.cs
file. -
Replace all the namespace declarations at the top of the file with this:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; using Aliencube.AzureFunctions.Extensions.OpenApi.Attributes; using Aliencube.AzureFunctions.Extensions.OpenApi.Enums; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.KeyVault; using Microsoft.Azure.KeyVault.Models; using Microsoft.Azure.Services.AppAuthentication; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using Newtonsoft.Json;
-
Replace the
GetSecrets
function with this.[FunctionName(nameof(GetSecrets))] [OpenApiOperation("list", "secret", Summary = "Gets the list of secrets", Description = "This gets the list of secrets.", Visibility = OpenApiVisibilityType.Important)] [OpenApiResponseBody(HttpStatusCode.OK, "application/json", typeof(SecretCollectionResponseModel), Summary = "Secret collection response.")] public static async Task<IActionResult> GetSecrets( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "secrets")] HttpRequest req, ILogger log) { IActionResult result; try { var response = await kv.GetSecretsAsync(keyVaultBaseUrl) .ConfigureAwait(false); var models = response.OfType<SecretItem>() .Select(p => new SecretResponseModel() { Name = p.Identifier.Name }) .ToList(); result = new OkObjectResult(new SecretCollectionResponseModel() { Items = models }); } catch (KeyVaultErrorException ex) { var statusCode = (int)ex.Response.StatusCode; var value = new { StatusCode = statusCode, Message = ex.Body?.Error?.Message }; result = new ObjectResult(value) { StatusCode = statusCode }; } catch (Exception ex) { var statusCode = (int)HttpStatusCode.InternalServerError; var value = new { StatusCode = statusCode, Message = ex.Message }; result = new ObjectResult(value) { StatusCode = statusCode }; } return result; }
-
Replace the
GetSecret
function with this.[FunctionName(nameof(GetSecret))] [OpenApiOperation("view", "secret", Summary = "Gets the secret", Description = "This gets the secret.", Visibility = OpenApiVisibilityType.Important)] [OpenApiParameter("name", In = ParameterLocation.Path, Required = true, Type = typeof(string), Description = "The secret name.")] [OpenApiResponseBody(HttpStatusCode.OK, "application/json", typeof(SecretResponseModel), Summary = "Secret response.")] public static async Task<IActionResult> GetSecret( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "secrets/{name}")] HttpRequest req, string name, ILogger log) { IActionResult result; try { var response = await kv.GetSecretAsync(keyVaultBaseUrl, name) .ConfigureAwait(false); var model = new SecretResponseModel() { Name = response.SecretIdentifier.Name, Value = response.Value }; result = new OkObjectResult(model); } catch (KeyVaultErrorException ex) { var statusCode = (int)ex.Response.StatusCode; var value = new { StatusCode = statusCode, Message = ex.Body?.Error?.Message }; result = new ObjectResult(value) { StatusCode = statusCode }; } catch (Exception ex) { var statusCode = (int)HttpStatusCode.InternalServerError; var value = new { StatusCode = statusCode, Message = ex.Message }; result = new ObjectResult(value) { StatusCode = statusCode }; } return result; }
-
Open the
OpenApiHttpTrigger.cs
file. -
Replace all the namespace declarations at the top of the file with this:
using System; using System.IO; using System.Net; using System.Reflection; using System.Threading.Tasks; using Aliencube.AzureFunctions.Extensions.OpenApi; using Aliencube.AzureFunctions.Extensions.OpenApi.Abstractions; using Aliencube.AzureFunctions.Extensions.OpenApi.Attributes; using Aliencube.AzureFunctions.Extensions.OpenApi.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; using Microsoft.OpenApi; using Newtonsoft.Json;
-
Within the namespace block –
namespace KeyVault.FunctionApp
, add the following class block.public static class OpenApiHttpTrigger { }
-
Within the class block, add the following fields.
private static AppSettings settings = new AppSettings(); private static IDocument doc = new Document(new DocumentHelper()); private static ISwaggerUI swaggerUI = new SwaggerUI();
-
Add two Function endpoints.
[FunctionName(nameof(RenderSwaggerDocument))] [OpenApiIgnore] public static async Task<IActionResult> RenderSwaggerDocument( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "swagger.json")] HttpRequest req, ILogger log) { var assembly = Assembly.GetExecutingAssembly(); var result = await doc.InitialiseDocument() .AddMetadata(settings.OpenApiInfo) .AddServer(req, settings.HttpSettings.RoutePrefix) .Build(assembly) .RenderAsync(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Json) .ConfigureAwait(false); var content = new ContentResult() { Content = result, ContentType = "application/json", StatusCode = (int)HttpStatusCode.OK }; return content; } [FunctionName(nameof(RenderSwaggerUI))] [OpenApiIgnore] public static async Task<IActionResult> RenderSwaggerUI( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "swagger/ui")] HttpRequest req, ILogger log) { var result = await swaggerUI.AddMetadata(settings.OpenApiInfo) .AddServer(req, settings.HttpSettings.RoutePrefix) .BuildAsync() .RenderAsync("swagger.json", settings.SwaggerAuthKey) .ConfigureAwait(false); var content = new ContentResult() { Content = result, ContentType = "text/html", StatusCode = (int)HttpStatusCode.OK }; return content; }
Use Visual Studio Code, deploy the Azure Function app onto Azure.
Alternatively, Azure CLI can be used for app deployment.
dotnet build . -c Release
dotnet publish . -c Release
cd bin/Release/netcoreapp2.1/publish
zip -r publish.zip .
az functionapp deployment source config-zip \
-n gib2019-fncapp-1 \
-g gib2019 \
--src publish.zip
Once deployed, this Swagger UI page should appear. Note the URL of swagger.json
file. Also download the swagger.json
file just in case.