Deploy Function App
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
| File | Description |
|---|---|
| main.tf | Function App, App Service Plan, Storage, App Insights |
| fabric-access.tf | Fabric workspace role assignment |
| variables.tf | Input variables |
| outputs.tf | Module outputs |
| providers.tf | Provider requirements |
Place in azure-function/terraform/.
Python Azure Function
| File | Description |
|---|---|
| function_app.py | Timer-triggered entry point |
| host.json | Function host configuration |
| requirements.txt | Python dependencies |
| copy_parquet/__init__.py | Package init |
| copy_parquet/service.py | Main S3→Azure copy logic |
| copy_parquet/uploader_base.py | Base uploader class |
| copy_parquet/uploader_onelake.py | OneLake DFS uploader |
| copy_parquet/uploader_storage_account.py | Storage Account uploader |
| copy_parquet/utils.py | Utility functions |
Place in azure-function/ alongside the Terraform directory.
What Gets Created
The azure-function/terraform module provisions:
| Resource | Purpose |
|---|---|
| App Service Plan | Linux Consumption (Y1) — serverless, pay-per-execution |
| Function App | Python 3.10, timer-triggered, system-assigned managed identity |
| Storage Account | Function App runtime state and metadata |
| Application Insights | Monitoring, logging, and telemetry |
| Log Analytics Workspace | Centralized 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"| STORAGEPrerequisites
- 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:
- Get the Function App’s principal ID:
terraform output function_app_identity_principal_id - In Fabric Portal, go to your workspace → Settings → Manage access
- 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:
- Create a Storage Account for Function App runtime
- Create a Function App (Python 3.10, Linux, Consumption plan)
- Enable System-Assigned Managed Identity
- Configure Application Settings (S3 credentials, upload destination, schedule)
- Package and deploy function code via ZIP Deploy
Application Settings
Configure these environment variables in the Function App:
S3 Settings:
| Setting | Value |
|---|---|
S3_ACCESS_KEY_ID | Your AWS access key |
S3_SECRET_ACCESS_KEY | Your AWS secret key |
S3_REGION | e.g., eu-west-1 |
S3_BUCKET | S3 bucket name |
S3_PREFIX | Optional path prefix |
Upload Settings (OneLake):
| Setting | Value |
|---|---|
UPLOAD_TYPE | onelake |
FABRIC_TENANT_ID | Azure AD tenant ID |
FABRIC_CLIENT_ID | OneLake app client ID |
FABRIC_CLIENT_SECRET | OneLake app client secret |
FABRIC_WORKSPACE_FQN | Workspace display name |
FABRIC_LAKEHOUSE_NAME | Lakehouse name |
Upload Settings (Storage Account):
| Setting | Value |
|---|---|
UPLOAD_TYPE | storageaccount |
STORAGE_CONNECTION_STRING | Storage account connection string |
STORAGE_ACCOUNT_URL | Storage account blob URL |
STORAGE_UPLOAD_CONTAINER | Container name (e.g., incoming) |
Schedule:
| Setting | Value |
|---|---|
COPY_PARQUET_SCHEDULE | CRON expression, e.g., 0 */15 * * * * |
MEASUREMENTS_TIME_RANGE_DAYS | Days to look back, e.g., 14 |
Timer Schedule Reference
The schedule uses Azure Functions CRON format with seconds:
| Expression | Description |
|---|---|
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 * * 0 | Weekly 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
Check Function Portal Link
terraform output function_address
Cost Estimate
| Component | Approximate 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
Destroying the Function App stops all automated data transfers. Your destination data (OneLake/Storage Account) is not affected.
Next Steps
→ Connect Power BI to visualize your data in Power BI Desktop