In my previous post, we've walked through how to run the Azure Functions app as a background process and locally generate the OpenAPI document from it within the GitHub Actions workflow. The OpenAPI document can be stored as an artifact and integrated with Power Platform or Azure API Management (APIM) later on.
- Generating OpenAPI Document from Azure Functions within CI/CD Pipeline
- Publishing OpenAPI Document from Azure Functions to Azure API Management within CI/CD Pipeline 👈
Throughout this post, I'm going to discuss how to publish the OpenAPI document to APIM, after locally running the Azure Functions app as a background process and generating the OpenAPI document within the GitHub Actions workflow.
NOTE: Let's use the sample app provided by the Azure Functions OpenAPI Extension repository.
Update GitHub Actions Workflow
The GitHub Actions workflow built in my previous post looks like the following (line #21-35). Let's assume that we use the Ubuntu runner and PowerShell.
name: Build
on:
push:
jobs:
build_and_test:
name: Build
runs-on: 'ubuntu-latest'
steps:
- name: Build solution
shell: pwsh
run: |
pushd MyFunctionApp
dotnet build . -c Release -v minimal
popd
- name: Generate OpenAPI document
shell: pwsh
run: |
cd MyFunctionApp
Start-Process -NoNewWindow func @("start","--verbose","false")
Start-Sleep -s 60
Invoke-RestMethod -Method Get -Uri http://localhost:7071/api/swagger.json | ConvertTo-Json -Depth 100 | Out-File -FilePath outputs/swagger.json -Force
Get-Content -Path outputs/swagger.json
cd ..
The OpenAPI document generated from the pipeline above has https://localhost:7071/api
as the default server URL. However, the actual server URL must be the format of https://<azure-functions-app>.azurewebsites.net/api
after deployment. Therefore, although you haven't deployed the function app yet, the generated OpenAPI document must have the deployed app URL. According to the doc, the extension offers the feature to apply the server URL.
- Set the environment variable,
OpenApi__HostNames
, so the actual server URL is added on top oflocalhost
(line #4). - Set the environment variable,
AZURE_FUNCTIONS_ENVIRONMENT
, toProduction
so thatlocalhost
is omitted and only the server URL is rendered as if the function app is running on Azure (line #5). - Generally speaking,
local.settings.json
is excluded from the repository because it's supposed to use for local development. But, it's required to run the function app locally as a background process. Therefore, it's always a good idea to create one. The revised action below copies thelocal.settings.sample.json
tolocal.settings.json
(line #12).
With these three points in mind, let's update the GitHub Action (line #4,5,12).
- name: Generate OpenAPI document
shell: pwsh
env:
OpenApi__HostNames: 'https://<azure-functions-app>.azurewebsites.net/api'
AZURE_FUNCTIONS_ENVIRONMENT: 'Production'
run: |
cd MyFunctionApp
mkdir outputs
# Create local.settings.json
cp ./local.settings.sample.json ./local.settings.json
Start-Process -NoNewWindow func @("start","--verbose","false")
Start-Sleep -s 60
Invoke-RestMethod -Method Get -Uri http://localhost:7071/api/swagger.json | ConvertTo-Json -Depth 100 | Out-File -FilePath outputs/swagger.json -Force
Get-Content -Path outputs/swagger.json -Raw
cd ..
As mentioned above, this action just copies the existing local.settings.sample.json
file to local.settings.json
, but it's not common. Therefore, in most cases, you should create the local.settings.json
file by yourself, and it MUST include the environment variable, FUNCTIONS_WORKER_RUNTIME
(line #3). In other words, the minimal structure of local.settings.json
looks like below (assuming you use the in-proc worker):
{
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
Finally, you will see the server URL applied from the OpenAPI document (line #4,16).
// OpenAPI v2
{
"swagger": "2.0",
"host": "<azure-functions-app>.azurewebsites.net",
"basePath": "/api",
"schemes": [
"https"
]
}
// OpenAPI v3
{
"openapi": "3.0.1",
"servers": [
{
"url": "https://<azure-functions-app>.azurewebsites.net/api"
}
]
}
Now, we've got the OpenAPI document from the CI/CD pipeline, with the actual server URL, without deploying the function app.
Publish to Azure API Management
This time, let's publish the OpenAPI document to APIM. Although there are many ways to do it, let's use Azure Bicep and deploy it through Azure CLI in this post. Here's the first part of the bicep file. Use the existing
keyword to get the existing APIM resource details.
// azuredeploy.bicep
param servicename string
resource apim 'Microsoft.ApiManagement/service@2021-08-01' existing = {
name: servicename
scope: resourceGroup(apiManagement.groupName)
}
Then, with these APIM details, declare the API resource.
// azuredeploy.bicep
param openapidoc string
resource apimapi 'Microsoft.ApiManagement/service/apis@2021-08-01' = {
name: '${apim.name}/my-api'
properties: {
type: 'http'
displayName: 'My API'
description: 'This is my API.'
path: 'myapi'
subscriptionRequired: true
format: 'openapi+json-link'
value: openapidoc
}
}
It looks simple, except those two attributes – format
and value
(line #12-13). Therefore, it's better to understand what those two attributes are. For more comprehensive details, you can visit the Azure Bicep Template Reference.
- TO use the JSON string parsed from the OpenAPI document:
- OpenAPI v2: Set the
format
value toswagger-json
. - OpenAPI v3: Set the
format
value toopenapi+json
. - Assign the JSON string to the
value
attribute.
- OpenAPI v2: Set the
- TO use the publicly accessible URL for the OpenAPI document:
- OpenAPI v2: Set the
format
value toswagger-link-json
. - OpenAPI v3: Set the
format
value toopenapi+json-link
. - Assign the publicly accessible URL to the
value
attribute.
- OpenAPI v2: Set the
Even if the first approach is doable, I wouldn't recommend it because it's too painful to parse the OpenAPI document to the JSON string that the bicep file can understand. Therefore, I would suggest using the second approach by storing the OpenAPI document to Azure Blob Storage right after it's generated from the pipeline.
Once you complete authoring the bicep file, run the Azure CLI command to deploy the API to APIM.
$servicename = "<my_apim_service_name>"
$openapidoc = https://<my_blob_storage_name>.blob.core.windows.net/<container>/openapi.json
az deployment group create `
-g <resource_group_name> `
-n <deployment_name> `
-f ./azuredeploy.bicep `
-p servicename=$servicename `
-p openapidoc=$openapidoc
You can confirm that the API is correctly deployed to APIM on Azure Portal.
So far, we've walked through how:
- To run Azure Functions app as a background process,
- To generate the OpenAPI document from the app by applying the deployed server URL without actually deploying it, and
- To publish the OpenAPI document to APIM within GitHub Actions workflow.
By doing so, you will be able to reduce some maintenance overheads by automating the integration part.