Toan Le

How to upload, download, and delete blob file with Azure Function in Python

2023-02-24

Azure Functions is a serverless computing service provided by Microsoft that allows you to run your code in response to events and triggers without worrying about infrastructure management. In this tutorial, we will go through the steps to implement an Azure Function using Python.

Prerequisites

To follow along with this tutorial, you will need the following:

  • Azure account
  • Azure Functions Core Tools
  • Python 3.6 or higher
  • Visual Studio Code

Step 1: Create Azure environment with Terraform

1. Create Service Principle

az ad sp create-for-rbac --name terraform --role Contributor --role "Role Based Access Control Administrator" \
--scopes /subscriptions/{your_subscription_id}

Replace {your_subscription_id} with your subscription id

2. Export variable in Powershell from Service Principle

$env:ARM_SUBSCRIPTION_ID = "{your_subscription_id}"
$env:ARM_TENANT_ID = "{tenant_id}"
$env:ARM_CLIENT_ID = "{client_id}"
$env:ARM_CLIENT_SECRET = "{client_secret}"

3. Deploy Terraform

variables.tf

variable "subscription_id" {
  type    = string
  default = "{your_subscription_id}"
}

variable "resource_group_name" {
  type    = string
  default = "example-rg"
}

variable "resource_group_location" {
  type    = string
  default = "East Asia"
}

variable "tag_environment" {
  type    = string
  default = "dev"
}

variable "example_fw_name" {
  type    = string
  default = "examplefw"
}

variable "function_name" {
  type    = string
  default = "examplefunction"
}

variable "storage_account_name" {
  type    = string
  default = "examplestorage"
}

variable "example_app_service_plan_name" {
  type    = string
  default = "exampleappserviceplan"
}

variable "example_appinsights_plan_name" {
  type    = string
  default = "exampleappinsights"
}

variable "key_vault_name" {
  type    = string
  default = "examplekeyvault"
}

variable "key_vault_secret_name" {
  type    = string
  default = "example-storage-account-connection-string"
}

Replace {your_subscription_id} with your subscription id

main.tf

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=3.84.0"
    }
  }
}

provider "azurerm" {
  features {
    key_vault {
      purge_soft_deleted_secrets_on_destroy = true
      recover_soft_deleted_secrets          = true
    }

    resource_group {
      prevent_deletion_if_contains_resources = false
    }
  }
  subscription_id = var.subscription_id
}

resource "azurerm_resource_group" "example" {
  name     = var.resource_group_name
  location = var.resource_group_location
  tags = {
    environment = var.tag_environment
  }
}

resource "random_string" "random" {
  length  = 10
  lower   = true
  numeric = false
  special = false
  upper   = false
}

resource "azurerm_storage_account" "example" {
  name                     = "${var.storage_account_name}${lower(random_string.random.id)}"
  resource_group_name      = azurerm_resource_group.example.name
  location                 = azurerm_resource_group.example.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
  min_tls_version          = "TLS1_2"
}

resource "azurerm_storage_container" "example" {
  name                  = "example"
  storage_account_name  = azurerm_storage_account.example.name
  container_access_type = "blob"
}

resource "azurerm_service_plan" "example" {
  name                = "${var.example_app_service_plan_name}${lower(random_string.random.id)}"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  os_type             = "Linux"
  sku_name            = "B1"
}

resource "azurerm_application_insights" "example" {
  name                = "${var.example_appinsights_plan_name}${lower(random_string.random.id)}"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  application_type    = "other"
}

resource "azurerm_linux_function_app" "example" {
  name                = "${var.function_name}${lower(random_string.random.id)}"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location

  storage_account_name       = azurerm_storage_account.example.name
  storage_account_access_key = azurerm_storage_account.example.primary_access_key
  service_plan_id            = azurerm_service_plan.example.id

  site_config {
    application_insights_key               = azurerm_application_insights.example.instrumentation_key
    application_insights_connection_string = azurerm_application_insights.example.connection_string
    application_stack {
      python_version = "3.11"
    }
  }

  identity {
    type = "SystemAssigned"
  }
}

data "azurerm_client_config" "current" {}

resource "azurerm_key_vault" "example" {
  name                       = "${var.key_vault_name}${lower(random_string.random.id)}"
  location                   = azurerm_resource_group.example.location
  resource_group_name        = azurerm_resource_group.example.name
  tenant_id                  = data.azurerm_client_config.current.tenant_id
  sku_name                   = "standard"
  soft_delete_retention_days = 7
}

resource "azurerm_key_vault_access_policy" "example" {
  key_vault_id = azurerm_key_vault.example.id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = data.azurerm_client_config.current.object_id


  key_permissions = [
    "Create",
    "Get",
    "List",
  ]

  secret_permissions = [
    "Set",
    "Get",
    "List",
    "Delete",
    "Purge",
    "Recover"
  ]
}

resource "azurerm_key_vault_access_policy" "example-function" {
  key_vault_id = azurerm_key_vault.example.id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = azurerm_linux_function_app.example.identity[0].principal_id

  key_permissions = [
    "Get",
  ]

  secret_permissions = [
    "Get",
  ]

  depends_on = [
    azurerm_key_vault_access_policy.example
  ]
}

resource "azurerm_role_assignment" "example" {
  scope                = azurerm_key_vault.example.id
  role_definition_name = "Reader"
  principal_id         = azurerm_linux_function_app.example.identity[0].principal_id

  depends_on = [
    azurerm_key_vault_access_policy.example-function
  ]
}

resource "azurerm_key_vault_secret" "example" {
  name         = "${var.key_vault_secret_name}${lower(random_string.random.id)}"
  value        = azurerm_storage_account.example.primary_connection_string
  key_vault_id = azurerm_key_vault.example.id
  depends_on = [
    azurerm_key_vault_access_policy.example
  ]
}

Step 2: Create Azure function in VSCode

1. Setup VSCode

  • Install Azure Functions extension for Visual Studio Code.
  • Create new fuctions from extension command image

2. Create upload function

import logging

import azure.functions as func
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from azure.storage.blob import BlobServiceClient


def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Begin to upload file.')

    secret_name = req.params.get('secret_name')
    vault_url = req.params.get('vault_url')
    logging.info('secret_name: %s' % secret_name)
    logging.info('vault_url: %s' % vault_url)

    credential = DefaultAzureCredential()
    client = SecretClient(vault_url=vault_url, credential=credential)

    retrieved_secret = client.get_secret(secret_name)

    for input_file in req.files.values():
        filename = input_file.filename

        logging.info('Filename: %s' % filename)

        blob_service_client = BlobServiceClient.from_connection_string(
            retrieved_secret.value)
        blob_client = blob_service_client.get_blob_client(
            container="example", blob=filename)
        blob_client.upload_blob(input_file.stream.read(), overwrite=True)

    return func.HttpResponse('Upload file to blob storage account successfully.')

Replace {vault_url}, {storage_connection_string} with your config

3. Create download function

import logging

import azure.functions as func
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from azure.storage.blob import BlobServiceClient


def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Begin to get blob data.')

    filename = req.params.get('filename')
    secret_name = req.params.get('secret_name')
    vault_url = req.params.get('vault_url')
    logging.info('filename: %s' % filename)
    logging.info('secret_name: %s' % secret_name)
    logging.info('vault_url: %s' % vault_url)

    credential = DefaultAzureCredential()
    client = SecretClient(vault_url=vault_url, credential=credential)

    retrieved_secret = client.get_secret(secret_name)

    blob_service_client = BlobServiceClient.from_connection_string(
        retrieved_secret.value)
    
    blob_client = blob_service_client.get_blob_client(
        container="example", blob=filename)

    data = blob_client.download_blob().readall()

    return func.HttpResponse(data, status_code=200, mimetype="application/octet-stream")

4. Create delete function

import logging

import azure.functions as func
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from azure.storage.blob import BlobServiceClient


def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Begin to get blob data.')

    filename = req.params.get('filename')
    secret_name = req.params.get('secret_name')
    vault_url = req.params.get('vault_url')
    logging.info('secret_name: %s' % secret_name)
    logging.info('vault_url: %s' % vault_url)
    
    credential = DefaultAzureCredential()
    client = SecretClient(vault_url=vault_url, credential=credential)

    retrieved_secret = client.get_secret(secret_name)

    blob_service_client = BlobServiceClient.from_connection_string(
        retrieved_secret.value)

    blob_client = blob_service_client.get_blob_client(
        container="example", blob=filename)

    blob_client.delete_blob()

    return func.HttpResponse()

5. requirements.txt

# DO NOT include azure-functions-worker in this file
# The Python Worker is managed by Azure Functions platform
# Manually managing azure-functions-worker may cause unexpected issues

azure-functions
azure-identity
azure-keyvault-secrets
azure-storage-blob

Copyright (c) 2024