Azure API Management | Logic App (Standard) Backend Using a Swagger Definition

Posted by Andrew Wilson on Thursday, February 1, 2024

Overview

GitHub Repository

After setting up a Logic App (Standard) Backend in Azure API Management (APIM) in my last post, I wanted to try and see if I could create a Swagger definition from a Standard Logic App which could then be used to simplify the API authoring process in APIM. This post shows my methods of doing so.

If you haven’t already I would recommend reading my previous post as this one will be working off of the building blocks of the last.

As with my previous post, the high-level architecture has not changed and neither has the overall aim as shown below. Overview

The design aims to abstract the backend from the api operations, i.e. the backend points to the Logic App and the individual operations point to the respective workflows. The design also specifies granular access to the workflow Shared-Access-Signature (sig) held in the applications specific KeyVault (to see further details on this, see Azure RBAC Key Vault | Role Assignment for Specific Secret). Furthermore, the additional required parameters that are necessary to call a workflow have been implemented through APIM policies to remove the need for callers to understand backend implementation.

I have opted for Infrastructure as Code (IaC) as my method of implementation, specifically Bicep. I have broken down the implementation of the diagram above into two parts, Application Deployment, and API Deployment.

Problem Space

Having come across the ARM REST API to generate a Swagger definition for a consumption Logic App, I went searching for the same functionality for Standard Logic Apps. Given a short time searching, I found the REST APIs situated under App Services with the Swagger generation API no where to be seen.

But would this stop me… No.

Knowing that I can retrieve a detailed definition of a Standard Logic App through the Azure Resource Manager, I started looking at methods of stripping out the detail that I would need to construct a Swagger Definition.

My chosen method to do this is through a PowerShell script using both ARM REST APIs and the az cli. This choice will allow me to run the script as non-interactive in future DevOps CI/CD pipelines.

Application Deployment

Our first step toward generating the Swagger definition for our Logic App is to deploy an instance of our Logic App to Azure. The process has not changed from the last post apart from the addition of step 4. As shown in the diagram below, step 4 is where we will use the PowerShell script that I have written to extract the core details of our Logic App and workflows to construct a Swagger Definition.

Application Deployment

The script has been designed to take a ControlFile.json that you update with one or more HTTP triggered workflows and API details to assist in the retrieval and generation of the definition. There is an example control file in my GitHub repository.

The SpecCreator.ps1 takes the following parameters:

<#
    .SYNOPSIS
	Creates a Swagger definition for a select Standard Logic App and its specified Workflows.

    .DESCRIPTION
	Uses a series of parameters to derive the Standard Logic App and the Workflows to include in the base Swagger definition template. 

    .PARAMETER SubscriptionId
    The ID of the Subscription that the Standard Logic App is Hosted in.

    .PARAMETER ResourceGroupName
    The Resource Group Name that the Standard Logic App is Hosted in.

    .PARAMETER LogicAppName
    The Name of the Standard Logic App to use for the Swagger Definition.

    .PARAMETER APITitle
    API Name used to define the set of operations.

    .PARAMETER APIDescription
    Description of the API and its set of operations.

    .PARAMETER APIVersion
    None-Mandatory - Specified version of the API. Default is 1.0.0.0

    .PARAMETER SpecTemplatePath
    None-Mandatory - Base path to the swagger definition template, doesn't include the name of the file.

    .PARAMETER ControlFile
    None-Mandatory - Base path to the json control file used to identify the set of Workflows to extract as operations for the swagger definition, doesn't include the name of the file.

	.PARAMETER InteractiveMode
	If specified will request the user to interactively login into Azure for az tooling.
    #>

The script then uses a series of ARM REST API calls to obtain the following detail:

  • Request Schema (if one is provided).
  • Workflow Definition.

Using the Workflow Definition, we can interrogate the Workflow Trigger for details such as:

  • HTTP Method.
  • Relative Paths.
    • Note: Path Parameters are expected to be wrapped with curly braces, for example:
      • /users/{id}
      • /cars/{carId}/drivers/{driverId}
      • cars/{carId}/drivers/{driverId}
      • /{carId}/{driverId}
      • {carId}

Once all the Workflow details have been obtained, the script uses an imported tokenised Swagger Definition to generate the respective Definition for your Standard Logic App and Workflows.

API Deployment

For the most part, the API Deployment has not changed. We are using the same Bicep to create the APIM Instance, APIM Backends/Named Values, and Policies. The main change to our Bicep Deployment occurs in step 2.1 as shown in the diagram below.

API Deployment

Rather than constructing the API and API Operations individually through Bicep Resources as done in the previous post, we are going to define the APIM API along with the generated Swagger Definition from earlier steps. APIM validates the Swagger Definition and uses it to construct the API and Operations on our behalf.

The three main differences to our API deployment are:

  1. We need to make use of the loadTextContent Bicep function to load the generated Swagger Definition into our Bicep Template.
    var swaggerDefinition = loadTextContent('../../SwaggerGenerator/GeneratedSpec.json')
    
  2. We need to change the Microsoft.ApiManagement/service/apis resource to accept our generated Swagger Definition:
    @description('Create the Logic App API in APIM - Loading in Swagger Definition')
    resource logicAppAPI 'Microsoft.ApiManagement/service/apis@2022-08-01' = {
      name: apiName
      parent: apimInstance
      properties: {
        displayName: apimAPIDisplayName
        subscriptionRequired: true
        path: apimAPIPath
        protocols: [
          'https'
        ]
        format: 'swagger-json'
        value: swaggerDefinition
      }
    }
    
  3. We need to remove the following Bicep Module call to create the API Operations as this will now be done for us.
    @description('Deploy logic App API operation')
    module logicAppAPIOperation 'Modules/apimOperation.azuredeploy.bicep' = [for operation in apimAPIOperations :{
      name: '${operation.name}-deploy'
      params: {
        parentName: '${apimInstance.name}/${logicAppAPI.name}'
        lgCallBackObject: listCallbackUrl(resourceId('Microsoft.Web/sites/hostruntime/webhooks/api/workflows/triggers', logicAppName, 'runtime', 'workflow', 'management', operation.lgWorkflowName, operation.lgWorkflowTrigger), '2022-09-01')
        operationDisplayName: operation.displayName
        operationMethod: operation.method
        operationName: operation.name
    
      }
    }]
    

Summary

The main benefit of using this method over constructing the API and Operations through Bicep is that we only need to define the backing API once.

If you would like to spin up a demo of this implementation or would like to use it as the basis of something you are working on, the source to all my work is in my GitHub repository along with a README that explains how to get started.

Have a go and see if this simplifies your Standard Logic App APIM Configurations.