Logic App ARM Template using Bicep

Microsoft is working on a Domain Specific Language (DSL) for abstracting the Azure ARM templates, https://github.com/Azure/bicep, Although the language is still in its infancy, it is a very useful language for generating ARM templates without the need to learn the complexities required. Furthermore, it allows reusable code to be implemented using modules.

Following some work that was done in the past with Terraform, which still required ARM templates to support it, it was imperative to look at Bicep as a supporting tool. To this regard, a simple Echo API used logic apps, which stores the responses in a blob was used as an experimentation playground.

Setting up the Datalake storage account

Before building the Logic App, which will require more knowledge of ARM template structure, a simple resource will be created. What can be simpler than a Storage Account.

To create a Storage Account using a minimalist ARM template, one would need to construct the following JSON:

  1. {
  2.     "type": "Microsoft.Storage/storageAccounts",
  3.     "apiVersion": "2020-08-01-preview",
  4.     "name": "[parameters('storageAccounts_name')]",
  5.     "location": "[resourceGroup().location]",
  6.     "kind": "StorageV2",
  7.     "sku": {
  8.         "name": "Standard_LRS",
  9.         "tier": "Standard"
  10.     },
  11.     "properties": {
  12.         "supportsHttpsTrafficOnly": true,
  13.         "isHnsEnabled": true,
  14.         "minimumTlsVersion": "TLS1_2",
  15.         "accessTier": "Hot"
  16.     }
  17. }

Translating the ARM Template above into Bicep, one will end up producing the following script:

  1. resource storage_example 'Microsoft.Storage/storageAccounts@2020-08-01-preview' = {
  2.   name: 'storexampleweeu001'
  3.   location: resourceGroup().location
  4.   kind: 'StorageV2'
  5.   sku: {
  6.     name: 'Standard_LRS'
  7.     tier: 'Standard'
  8.   }
  9.   properties: {
  10.     supportsHttpsTrafficOnly: true
  11.     isHnsEnabled: true
  12.     minimumTlsVersion: 'TLS1_2'
  13.     accessTier: 'Hot'
  14.   }
  15. }

It is immediately evident the similarity between the two scripts. First the type and the apiVersion are defined in the Bicep template to define what type of resource is required, Following that the template is very similar. Some of the minor noticeable differences are:

  • Line 3 in the Bicep template makes it easier to determine that we are using a function to retrieve the hosting resource group location. While looking at the equivalent line in the ARM template, line 5, the first impression is that it is a normal string .
  • Another noticeable difference is that strings in Bicep templates make use of single quotes, instead of the double quotes.

Other than the 2 differences mentioned above, the structure and the details are exactly the same.

Setting up the Blob storage for storing the data

Once the Storage Account has been created, a blob container or in this case a Datalake V2 container needs to initialised to host the blobs created by the Logic App.

The ARM template to create a blob container is as follows:

    "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
    "apiVersion": "2020-08-01-preview",
    "name": "[format('{0}/default/logicappstore', parameters('storageAccounts_name'))]",
    "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_name')))]"

Here the ARM template starts getting a little tougher to understand for beginners. In line 4, the name is dynamically created, and although the container will be named ‘logicappstore‘ it needs to be referenced as a folder structure with the Storage Account name being the root location. Furthermore, the line is making use of the string format function to construct the name. Finally in line 6, the resourceId() function is introduced to fetch the unique identifier of the storage account, which is normally a subscription based path. However during ARM template coding the Id of the storage account might not be known, although one can try to guess.

Using Bicep to create the blob container, the first benefits of the language can start to emerge.

  1. resource storage_blob_container 'Microsoft.Storage/storageAccounts/blobServices/containers@2020-08-01-preview' = {
  2.   name: '${storage_example.name}/default/logicappstore'
  3. }

The first noticeable difference is the lack of need to define the depends on section. The depends on section in the ARM template will be used automatically added by the Bicep engine. The second difference is the way the name field is defined. In the Bicep language instead of using the format() function, which is still possible the string interpolation is used. This makes the field value more readable. Finally, Bicep is able to refer to the name of the storage account by using the resource reference name provided as a second parameter to the resource command and accessing the name property.

Setting up API connection to the Azure Blob Storage

The logic app will require a connection to the Azure blob storage created earlier.

  1. resource logic_app_blob_connection 'Microsoft.Web/connections@2016-06-01' = {
  2.   name: 'api-example-weeu-001'
  3.   location: resourceGroup().location
  4.   properties: {
  5.     displayName: 'api-example-weeu-001'
  6.     parameterValues: {
  7.       accountName: storage_example.name
  8.       accessKey: listKeys(storage_example.id, '2019-06-01').keys[0].value
  9.     }
  10.     api: {
  11.       id: concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azureblob')
  12.     }
  13.   }
  14. }

Notice the use of listKeys and concat as functions within the Bicep language. This makes the code look more like the traditional programming language than the ARM template string format.

Setting up the Logic App

Now that the background work has been scripted, it is time to build the logic app ARM template.

  1. resource logic_app_example 'Microsoft.Logic/workflows@2019-05-01' = {
  2.   name: 'logic-example-weeu-001'
  3.   location: resourceGroup().location
  4.   properties: {
  5.     definition: {
  6.       '$schema': 'https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#'
  7.       triggers: {
  8.         'http_request': {
  9.           type: 'request'
  10.           kind: 'http'
  11.           inputs: {
  12.             schema: {
  13.               '$schema': 'http://json-schema.org/draft-04/schema#'
  14.               type: 'object'
  15.               properties: {
  16.                 hello: {
  17.                   type: 'string'
  18.                 }
  19.               }
  20.               required: [
  21.                 'hello'
  22.               ]
  23.             }
  24.           }
  25.           operationOptions: 'EnableSchemaValidation'
  26.         }
  27.       }
  28.       actions: {
  29.         'HTTP_Response': {
  30.           type: 'Response'
  31.           inputs: {
  32.             ...
  33.           }
  34.           runAfter: {
  35.             'Create_Blob': [
  36.               'Succeeded'
  37.             ]
  38.           }
  39.         }
  40.         'Create_Blob': {
  41.           inputs: {
  42.             ...
  43.           }
  44.           runAfter: {}
  45.           runtimeConfiguration: {
  46.             contentTransfer: {
  47.               transferMode: 'Chunked'
  48.             }
  49.           }
  50.           type: 'ApiConnection'
  51.         }
  52.       }
  53.       parameters: {
  54.         '$connections': {
  55.           defaultValue: {}
  56.           type: 'Object'
  57.         }
  58.       }
  59.     }
  60.     parameters: {
  61.       '$connections': {
  62.         value: {
  63.           azureblob: {
  64.             connectionId: logic_app_blob_connection.id
  65.             connectionName: logic_app_blob_connection.name
  66.             id: concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azureblob')
  67.           }
  68.         }
  69.       }
  70.     }
  71.   }
  72. }

Once the script is complete, it needs to be built to generate the Incremental ARM template. Finally the ARM template can be deployed to a resource group.

Testing the ECHO API

Using Postman the Logic App can be called and tested.

Postman test of the Echo API
Figure 1: Postman test of the Echo API

The Logic App also created a blob in the Storage Account using the timestamp as the blob name.

Blob Created from API Call
Figure 2: Blob Created from API Call

The code for this example can be found at https://github.com/kdemanuele/bicep-echo-api-logic-app