Event-driven: Azure Functions and Logic Apps

Varun Tomar
7 min readFeb 5, 2022

I am sure everyone has heard of the words “serverless” and “event-based”. All major cloud providers have their own set of tools to accomplish serverless. From AWS we have Lambda and Step Functions(coordinate multiple AWS services into serverless workflows), from Azure we have Functions and Logic Apps(same as AWS Step Functions), GCP has Cloud Functions. In this post, I am discussing Azure Functions and Logic Apps.

Azure Functions:

There are multiple ways to create and test Azure Functions. One tool I prefer using for Azure-related stuff is VSCode. It’s a great free product and tightly integrated with Azure. So, how do we start with Functions in Azure?

Pre-reqs:

  • Azure subscription
  • virtualenv running python 3.6
  • Recommend using vscode

Create Function app:

az functionapp create --consumption-plan-location westus --name c7ndemo --os-type Linux -g demo-rg -s demostg --runtime pythonYour Linux function app 'c7ndemo', that uses a consumption plan has been successfullycreated but is not active until content is published usingAzure Portal or the Functions Core Tools.

Run the above command and refresh the Function block in vscode and you would see the c7ndemo function app. If you notice the Function app is there but nothing inside ‘Functions’. The next step is to create a Function.

Note:

  • App name must be unique
  • Resource Group and Storage account must pre-exist
  • Create a Service plan or use a consumption plan and specify a location

Create Function project:

A function project is a container for one or more individual functions that each responds to a specific trigger. All functions in a project share the same local and hosting configurations. There are multiple options that can be passed to project initialization.

Initialize a project:

func init ProjectName --python

Found Python version 3.6.0 (python).
Writing .gitignore
Writing host.json
Writing local.settings.json
Writing /Users/demo/support/functions/http-stg/LocalFunctionProj/.vscode/extensions.json

Create a function:

From inside the project directory, run func new there are multiple ways in which to trigger a Function:

func new
Select a number for template:
1. Azure Blob Storage trigger
2. Azure Cosmos DB trigger
3. Azure Event Grid trigger
4. Azure Event Hub trigger
5. HTTP trigger
6. Azure Queue Storage trigger
7. Azure Service Bus Queue trigger
8. Azure Service Bus Topic trigger
9. Timer trigger

In my case, I am using HttpTrigger

Choose option: 5
HTTP trigger
Function name: [HttpTrigger]
Writing /private/tmp/test/LocalFunctionProj/HttpTrigger/__init__.py
Writing /private/tmp/test/ProjectName/HttpTrigger/function.json
The function "HttpTrigger" was created successfully from the "HTTP trigger" template.

Validate a function:

Now if you run: func start

[4/29/2020 4:21:38 PM] python process with Id=45561 started
[4/29/2020 4:21:38 PM] Generating 1 job function(s)
[4/29/2020 4:21:38 PM] Found the following functions:
[4/29/2020 4:21:38 PM] Host.Functions.HttpTrigger
[4/29/2020 4:21:38 PM]
[4/29/2020 4:21:38 PM] Initializing function HTTP routes
[4/29/2020 4:21:38 PM] Mapped function route 'api/HttpTrigger' [get,post] to 'HttpTrigger'
[4/29/2020 4:21:38 PM]
[4/29/2020 4:21:38 PM] Host initialized (176ms)
[4/29/2020 4:21:38 PM] Host started (184ms)
[4/29/2020 4:21:38 PM] Job host started
Hosting environment: Production
Content root path: /private/tmp/test/LocalFunctionProj
Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.
Http Functions:HttpTrigger: [GET,POST] http://localhost:7071/api/HttpTrigger.....
.....
.....
[4/29/2020 4:21:43 PM] Host lock lease acquired by instance ID '000000000000000000000000C5E07F0C'.

Two ways to validate:

  • curl:
curl --header "Content-Type: application/json" \
--request POST \
--data '{"name":"demo"}' \
http://localhost:7071/api/HttpTrigger

Binding to Blob Storage:

In the above case, I am using a default function with no changes, once you have verified that you are getting the above output let’s make some changes and write output to a Blob Storage. Three files need to be changed: function.json, __init__.py , and local.settings.json

Original function.json file:

{
"scriptFile": "__init__.py",
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}

Updated function.json file:

You can notice that I added a binding of type “blob”. There are more parameters that can be passed but this is minimum.

{
"scriptFile": "__init__.py",
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "$return"
},
{
"name": "otheroutput",
"type": "blob",
"direction": "out",
"path": "test/output/demo"
}

]
}

Original __init__py file:

import loggingimport azure.functions as funcdef main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
name = req.params.get('name')
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
return func.HttpResponse(f"Hello {name}!")
else:
return func.HttpResponse(
"Please pass a name on the query string or in the request body",
status_code=400
)

Updated __init__py file:

Few things to note:

  • Variable added to function argsotheroutput , this is the name that was defined in the function.json file
  • Added lines to get context information, this is just in case we need that information later.
  • To write to Blob added otheroutput.set(name)
import loggingimport azure.functions as funcdef main(req: func.HttpRequest, otheroutput: func.Out[str], context: func.Context):
logging.info("-" * 50)
logging.info('Python HTTP trigger function processed a request.')
logging.info(context.invocation_id)
logging.info(context.function_directory)
logging.info(context.function_name)
logging.info("-" * 50)

name = req.params.get('name')
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
otheroutput.set(name)
return func.HttpResponse(f"Hello {name}!")
else:
return func.HttpResponse(
"Please pass a name on the query string or in the request body",
status_code=400
)

Original local.settings.json file:

{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"AzureWebJobsStorage": "{AzureWebJobsStorage}"
}
}

Updatedlocal.settings.json file:

{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=c7ndemo;AccountKey=xxxxxx==;EndpointSuffix=core.windows.net"
}
}

As you can see I changed AzureWebJobsStorage to point to a key that I got from the storage account where I want to write the output. Start function againfunc start and curl or browse and then go to Blob storage and under test/output/demo you should see a text file with the content demo.

Once you have validated it's working as expected, it's time to push to Azure.

Deploy Function:

You can either do:func azure functionapp publish c7ndemo , or

In vscode under Functions: Deploy to Function App

The above will result in an endpoint something like this: https://xxxxx.azurewebsites.net/api/HttpTrigger?code=xxxxxx==

Validate if our event-driven architecture worked either using browse or curl and you should see data getting written into Blob. If you want to see logs you can go to Function App -> Select the Function (HttpTrigger) -> Monitor and then select the ‘Date’ for which you want to see the ‘Invocation Details’ and you can see the logs output there.

That’s all that is required to run a Function in Azure and create Storage binding.

One thing that took me time to figure out was, how local.settings.json to file content is gets passed to the function. So, the file content is getting passed as Application settings.

Next, let's see how to use Azure Logic Apps. We will need the Azure function that we created above as part of the deployment.

Logic Apps:

Logic Apps is a service that helps users automate, and orchestrate tasks, and workflows and helps you integrate apps, systems, and services.

Once you have created a Logic Apps. You can choose the actions that you want to perform. In this case, I am getting a JSON payload on the HTTP POST URL.

In our case, I am triggering two downstream events:

  • Azure Function(which is from above with input changes, can be found here in repo)
  • Sending emails using Gmail.

As you can see the JSON payload is coming from CloudCustodian and triggering HTTP hook and getting passed to Azure Functions and Email.

To update the email to read from JSON payload. Select the Send email(v2) step and then click Code view

Change the body section as below:

Switch back to Designer view and you would get the updated template:

Conclusion:

I am a big fan of event-driven and serverless, Azure Functions and Logic Apps are a great way to accomplish it. Feel free to drop a message if I missed something or if you have questions.

--

--