Deploy Function App

Deploy the Azure Function App that copies Parquet files from the Haltian IoT S3 bucket to OneLake or Azure Storage Account.

This guide deploys the Azure Function App that automatically copies Parquet files from the Haltian IoT Data API S3 bucket to your chosen Azure destination. You must complete either Deploy OneLake or Deploy Storage Account before proceeding.

Download Source Files

You need both the Terraform module and the Python function source:

Terraform Module

FileDescription
main.tfFunction App, App Service Plan, Storage, App Insights
fabric-access.tfFabric workspace role assignment
variables.tfInput variables
outputs.tfModule outputs
providers.tfProvider requirements

Place in azure-function/terraform/.

Python Azure Function

FileDescription
function_app.pyTimer-triggered entry point
host.jsonFunction host configuration
requirements.txtPython dependencies
copy_parquet/__init__.pyPackage init
copy_parquet/service.pyMain S3→Azure copy logic
copy_parquet/uploader_base.pyBase uploader class
copy_parquet/uploader_onelake.pyOneLake DFS uploader
copy_parquet/uploader_storage_account.pyStorage Account uploader
copy_parquet/utils.pyUtility functions

Place in azure-function/ alongside the Terraform directory.

What Gets Created

The azure-function/terraform module provisions:

ResourcePurpose
App Service PlanLinux Consumption (Y1) — serverless, pay-per-execution
Function AppPython 3.10, timer-triggered, system-assigned managed identity
Storage AccountFunction App runtime state and metadata
Application InsightsMonitoring, logging, and telemetry
Log Analytics WorkspaceCentralized log storage
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#F6FAFA', 'primaryTextColor': '#143633', 'primaryBorderColor': '#143633', 'lineColor': '#143633', 'secondaryColor': '#C7FDE6', 'tertiaryColor': '#73F9C1', 'clusterBkg': '#ffffff', 'clusterBorder': '#143633', 'edgeLabelBackground': '#ffffff'}}}%%
flowchart TB
    subgraph S3["Haltian IoT"]
        S3Bucket["fa:fa-database S3 Bucket<br/>Parquet Files"]
    end

    subgraph Azure["Customer Azure"]
        subgraph FuncInfra["Function App Infrastructure"]
            ASP["App Service Plan<br/>Consumption Y1"]
            FA["fa:fa-cogs Function App<br/>Python 3.10 · Managed Identity"]
        end

        subgraph Mon["Monitoring"]
            AI["fa:fa-chart-line Application Insights"]
            LAW["Log Analytics"]
        end

        subgraph Dest["Upload Destination"]
            ONELAKE["fa:fa-layer-group OneLake"]
            STORAGE["fa:fa-hdd Storage Account"]
        end
    end

    S3Bucket -->|"Timer Trigger"| FA
    FA --> AI
    AI --> LAW
    ASP --> FA
    FA -.->|"onelake mode"| ONELAKE
    FA -.->|"storageaccount mode"| STORAGE

Prerequisites

  • Terraform ≥ 1.5.0
  • Azure CLI authenticated (az login)
  • Subscription Contributor permissions
  • Completed infrastructure deployment: OneLake or Storage Account
  • S3 credentials from Haltian (access key + secret key)

Deployment with Terraform

Step 1: Prepare Configuration

If you ran the terraform output function_app_template_tfvars command from the infrastructure step, you already have a terraform.tfvars file in azure-function/terraform/. Open it and add your S3 credentials:

# AWS S3 Configuration (from Haltian)
s3_access_key_id       = "YOUR_S3_ACCESS_KEY_ID"
s3_secret_access_key   = "YOUR_S3_SECRET_ACCESS_KEY"
s3_bucket              = "YOUR_S3_BUCKET_NAME"
s3_region              = "eu-west-1"        # S3 bucket region
s3_prefix              = ""                 # Optional: path prefix within bucket

# Schedule (Azure Functions CRON with seconds)
copy_parquet_schedule        = "0 */15 * * * *"   # Every 15 minutes
measurements_time_range_days = "14"               # Days to look back (1-365)

If you don’t have the template, create terraform.tfvars with the full configuration. See the Configuration Reference for all variables.

Step 2: Deploy

cd azure-function/terraform

terraform init
terraform plan
terraform apply

Deployment takes approximately 5–7 minutes.

Step 3: Verify

# Get Function App name
FUNCTION_APP=$(terraform output -raw function_app_name)
RESOURCE_GROUP=$(terraform output -raw resource_group_name)

# Check status
az functionapp show \
  --name $FUNCTION_APP \
  --resource-group $RESOURCE_GROUP \
  --query "{name: name, state: state, hostName: defaultHostName}" \
  --output table

# List deployed functions
az functionapp function list \
  --name $FUNCTION_APP \
  --resource-group $RESOURCE_GROUP \
  --output table

Step 4: Grant Workspace Access (OneLake only)

For OneLake mode, the Function App’s managed identity needs access to the Fabric workspace. The Terraform module attempts this automatically, but if it fails:

  1. Get the Function App’s principal ID:
    terraform output function_app_identity_principal_id
    
  2. In Fabric Portal, go to your workspace → SettingsManage access
  3. Add the principal ID with Contributor role

Deployment via Azure Portal

For manual deployment without Terraform, see the full Azure Portal guides. The key steps are:

  1. Create a Storage Account for Function App runtime
  2. Create a Function App (Python 3.10, Linux, Consumption plan)
  3. Enable System-Assigned Managed Identity
  4. Configure Application Settings (S3 credentials, upload destination, schedule)
  5. Package and deploy function code via ZIP Deploy

Application Settings

Configure these environment variables in the Function App:

S3 Settings:

SettingValue
S3_ACCESS_KEY_IDYour AWS access key
S3_SECRET_ACCESS_KEYYour AWS secret key
S3_REGIONe.g., eu-west-1
S3_BUCKETS3 bucket name
S3_PREFIXOptional path prefix

Upload Settings (OneLake):

SettingValue
UPLOAD_TYPEonelake
FABRIC_TENANT_IDAzure AD tenant ID
FABRIC_CLIENT_IDOneLake app client ID
FABRIC_CLIENT_SECRETOneLake app client secret
FABRIC_WORKSPACE_FQNWorkspace display name
FABRIC_LAKEHOUSE_NAMELakehouse name

Upload Settings (Storage Account):

SettingValue
UPLOAD_TYPEstorageaccount
STORAGE_CONNECTION_STRINGStorage account connection string
STORAGE_ACCOUNT_URLStorage account blob URL
STORAGE_UPLOAD_CONTAINERContainer name (e.g., incoming)

Schedule:

SettingValue
COPY_PARQUET_SCHEDULECRON expression, e.g., 0 */15 * * * *
MEASUREMENTS_TIME_RANGE_DAYSDays to look back, e.g., 14

Timer Schedule Reference

The schedule uses Azure Functions CRON format with seconds:

ExpressionDescription
0 */15 * * * *Every 15 minutes
0 0 * * * *Every hour at :00
0 0 */6 * * *Every 6 hours
0 30 2 * * *Daily at 2:30 AM
0 0 0 * * 0Weekly on Sunday at midnight

Format: {second} {minute} {hour} {day} {month} {day-of-week}

Monitoring

View Function Logs

# Stream logs in real-time
az functionapp log tail \
  --name $FUNCTION_APP \
  --resource-group $RESOURCE_GROUP

# Query Application Insights
az monitor app-insights query \
  --app $(terraform output -raw application_insights_app_id) \
  --analytics-query "traces | where timestamp > ago(1h) | order by timestamp desc | take 20" \
  --output table
terraform output function_address

Cost Estimate

ComponentApproximate Cost
Function App (Consumption)~€0–20/month
Function Storage Account~€0.50/month
Application Insights~€0–10/month
Log Analytics~€0–5/month
Total~€1–36/month

Cleanup

To remove the Function App while keeping the infrastructure:

cd azure-function/terraform
terraform destroy

Next Steps

Connect Power BI to visualize your data in Power BI Desktop