Deploy an API running on Azure Functions using IaC & Azure DevOps
Tags: azure, javascript, tutorial, devops
Published: 2022-02-07 16:09:23

Azure Functions is a serverless event based compute engine that can execute almost any logic using blocks of code on demand commonly referred to as Functions. For this tutorial we will be building a simple web api using JavaScript using an http trigger. The API will return a JSON formatted CV using by hitting the endpoint through an HTTP request using the "Get" method.

## Objectives

All the code for this tutorial is located in my public GitHub repository. We will be running through the following:

- Develop the function app locally using Azure Function Core Tools

- Deploy the Azure Function infrastructure resources by running Terraform locally.

- Deploy the Azure Function as a package using an Azure DevOps build pipeline

- Test the function in Azure by calling the public HTTP endpoint.

**Architecture**

The function will be deployed in a [consumption](https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale) plan which allows it to scale automatically and means you only pay for the compute when the function is actually running. 

The below schematic shows the infrastructure that will be deploying and what the solution will look like:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kngynhtp2eylmbcexy30.png)

Some points worth noting regarding the setup

- The function is running in a consumption plan so vNet injection is not available.

- The function will be running from a package stored in Azure Blobs (More info [here](https://docs.microsoft.com/en-us/azure/azure-functions/run-functions-from-deployment-package))

- Storage account will have no firewall enabled as the function app is not vNET integrated. This is not recommended for enterprise scenarios and you should use a dedicated service plan with vNET injection.
 

## Pre-Requisites

There are some pre-requisites required before you can start the deployment:

- Git Repository (Azure Repos used for this tutorial)
- Access to Azure DevOps Pipelines
- Azure Function Core Tools (Install [here](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Cmacos%2Ccsharp%2Cportal%2Cbash))
- Node.js
- Azure CLI (Install [here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)) 
- Terraform version 1 or above.
- Azure Subscription

## Deployment Steps

We will go through the deployment steps in stages. The actual deployment time is around 15-30 mins if you have all the pre-requisites so fairly quick to get this up and running.

**Deploy the Infrastructure**

We will be deploying the following resources through Terraform

- Azure Resource Group
- App Service Plan (Consumption Based)
- Azure Function App (Linux)
- Azure Storage Account

There is a terraform template that I put together which can be re-used in my Github repository [here](https://github.com/lakovmartin/azure/blob/main/azure_functions/cv-function/iac/main.tf)

1: Change the variables and give your resources unique names (Line 19, 25, 31, 37) 
3: Authenticate to your Azure tenant using CLI (az login) and set your subscription (az account set -s <subscriptionname>)
2: Run `Terraform Init`
3: Run `Terraform Plan`
4: Review the plan and run `Terraform Apply`

The resources should now be deployed in Azure.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p08rybzl3o0mmxu48sp8.png)
 
**Create your local Function Project**

1: Create the following folder structure:
`azure_functions`

2: CD into the azure_functions subfolder and initialise the function project by running `func init cv-function -- javascript`. This will create a local functions project using javascript.

3: Next we need to add a function to our functions project. CD into the cv-function folder and run the following command `func new --name cv --template "HTTP Trigger" --authLevel anonymous`. This will create a subfolder called cv with an http trigger binding and anonymous authentication meaning anyone will be able to call the API which is fine for testing but not for enterprise deployments.

4: Next we need to edit the index.js which defines that function that will be triggered based on our binding (http request). Copy and paste the following code into the index.js file over writing it's existing contents:

```
module.exports = function (context, req) {
    jsonData = {
        "basics": {
            "name": "John Doe",
            "label": "Programmer",
            "image": "",
            "email": "john@gmail.com",
            "phone": "(912) 555-4321",
            "url": "https://johndoe.com",
            "summary": "A summary of John Doe…",
            "location": {
                "address": "2712 Broadway St",
                "postalCode": "CA 94115",
                "city": "San Francisco",
                "countryCode": "US",
                "region": "California"
            },
            "profiles": [{
                "network": "Twitter",
                "username": "john",
                "url": "https://twitter.com/john"
            }]
        },
        "work": [{
            "name": "Company",
            "position": "President",
            "url": "https://company.com",
            "startDate": "2013-01-01",
            "endDate": "2014-01-01",
            "summary": "Description…",
            "highlights": [
                "Started the company"
            ]
        }],
        "volunteer": [{
            "organization": "Organization",
            "position": "Volunteer",
            "url": "https://organization.com/",
            "startDate": "2012-01-01",
            "endDate": "2013-01-01",
            "summary": "Description…",
            "highlights": [
                "Awarded 'Volunteer of the Month'"
            ]
        }],
        "education": [{
            "institution": "University",
            "url": "https://institution.com/",
            "area": "Software Development",
            "studyType": "Bachelor",
            "startDate": "2011-01-01",
            "endDate": "2013-01-01",
            "score": "4.0",
            "courses": [
                "DB1101 - Basic SQL"
            ]
        }],
        "awards": [{
            "title": "Award",
            "date": "2014-11-01",
            "awarder": "Company",
            "summary": "There is no spoon."
        }],
        "certificates": [{
            "name": "Certificate",
            "date": "2021-11-07",
            "issuer": "Company",
            "url": "https://certificate.com",
        }],
        "publications": [{
            "name": "Publication",
            "publisher": "Company",
            "releaseDate": "2014-10-01",
            "url": "https://publication.com",
            "summary": "Description…"
        }],
        "skills": [{
            "name": "Web Development",
            "level": "Master",
            "keywords": [
                "HTML",
                "CSS",
                "JavaScript"
            ]
        }],
        "languages": [{
            "language": "English",
            "fluency": "Native speaker"
        }],
        "interests": [{
            "name": "Wildlife",
            "keywords": [
                "Ferrets",
                "Unicorns"
            ]
        }],
        "references": [{
            "name": "Jane Doe",
            "reference": "Reference…"
        }],
        "projects": [{
            "name": "Project",
            "description": "Description…",
            "highlights": [
                "Won award at AIHacks 2016"
            ],
            "keywords": [
                "HTML"
            ],
            "startDate": "2019-01-01",
            "endDate": "2021-01-01",
            "url": "https://project.com/",
            "roles": [
                "Team Lead"
            ],
            "entity": "Entity",
            "type": "application"
        }]
    }

    context.res = {
        body: JSON.stringify(jsonData, null, 2)
    };
    context.done();
};
```
Here we are using a JSON resume schema which you can edit with your details if you want to expose your CV as an API publicly. 

5: The input and output bindings are located in a file called function.json which also defines our trigger which is http. The output will be a the JSON data in the previous step.

```
{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

```
**Test the function locally**

Next we will test the function locally. 

1: CD back into the `cv-function` folder and run `func start`. The function will initialise locally:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/na7bi37pmn06ifk1v9ht.png)

2: Copy the local host URL and paste it into your browser. You should see an output of the JSON data we inserted previously:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2r4f9v9nyuu7wbq6joy8.png)

3: You can also use involve the request using PowerShell by running  `Invoke-RestMethod  -Method Get -Uri http://localhost:7071/api/cv`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5t13xz4rulom7dve6hpg.png)

**Deploy the Function to Azure**

We are now ready to deploy the package containing our Function app to Azure. Make sure you commit all of your changes to your Azure Git Repository

You can find a deployment pipeline I put together in my [Github](https://github.com/lakovmartin/azure/blob/main/azure_functions/cv-function/iac/function-deploy.yaml)  that can be used as a base by just replacing a couple of variables

1: Add the pipeline to your Git and replace the following variables:

Line 12 
This should be the service connection used for the deployment. It must have contributor permissions over the RG we created earlier

```
  # Azure Resource Manager connection created during pipeline creation
  azureSubscription: 'exampleAzureSubscription'
```

Line 15 
This is the name of the Azure Function App we deployed earlier using Terraform.

```
  # The name of the Azure Function App Resource
  functionAppName: 'exampleFunctionAppName'
```


Line 24
The default working directory is the the folder of your functions project.

```
  # The default working directory where your Function App is located
  workingDirectory: '$(System.DefaultWorkingDirectory)/cv-functions'
```
2: Run the pipeline and wait for the deployment to succeed.
       
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/17tvigyxedfvdbjxts6z.png)
 

Your Function should now be deployed and you can access it by hitting the Function App URL or making an HTTP Post request:

`https://<function-app-name>.net/api/<function-name>
`
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3hzcdwl9lv1zgdg6jm1.png)

**Part 2 will deploy the Function using Github which is where we will see Enterprises start to migrate to over the next few years.**