- name
- cloud-deployment-and-testing
- description
- Rules for optional cloud deployment and testing of Logic Apps Standard projects. Covers ARM/Bicep generation, deployment method, app settings management, cloud test execution, and reporting.
# Skill: Cloud Deployment and Testing
> **Purpose**: Authoritative rules for the optional cloud deployment and testing task. Follow exactly.
---
## 1. Task Nature
This task is OPTIONAL. The user decides whether to execute or skip it via the UI. It is excluded from "Execute All" and handled separately.
---
## 2. Deployment Steps
1. **Generate ARM/Bicep template** that deploys the Logic App Standard resource with all required dependencies.
2. **Deploy using `az deployment group create`** — do NOT use zip deploy for infrastructure.
3. **ALL app settings MUST be in the Bicep template** — do NOT use `az webapp config appsettings set` as a separate step. Bicep redeploys REPLACE all settings. Any setting not in the template will be wiped on the next deploy.
4. For File System scenarios, add the cloud-specific app settings and connection values required by Azure. Do NOT assume the local file-system mount setup is enough for cloud testing.
5. For File System connections in Azure using the **built-in service provider** (`/serviceProviders/FileSystem`), use Azure Files mounts — no username/password needed in `connections.json`. The `mountPath` app setting and `azureStorageAccounts` site config handle authentication.
6. For File System mount paths in Azure, do NOT use `/home`, `/home/site`, or `/home/site/wwwroot`. On Windows Logic Apps, mount paths must be under `/mounts/` (e.g., `/mounts/edi-files`).
### 2.1 Critical App Settings Rules
These rules prevent the most common deployment failures:
- **`FUNCTIONS_WORKER_RUNTIME` MUST be `dotnet`** — Logic Apps Standard uses `dotnet` for in-process .NET custom code. Use the same value as `local.settings.json`. Do NOT use `dotnet-isolated` or `node`.
- **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** and **`WEBSITE_CONTENTSHARE`** are REQUIRED for WS1 (WorkflowStandard) plans. Without them, the runtime won't start.
- **`WEBSITE_CONTENTSHARE`** should be set to the Logic App name (e.g., `la-my-app`). Azure auto-creates this file share for runtime content.
- **`FUNCTIONS_EXTENSION_VERSION`** MUST be `~4`.
- **`APP_KIND`** MUST be `workflowapp`.
- **Never set `ProjectDirectoryPath`** in cloud — it's for local `func start` only.
- **`@secure()` parameters with `&` characters** (like Integration Account callback URLs) must be placed in the Bicep parameters file, NOT passed inline via shell arguments. The `&` is interpreted as a shell operator and truncates the value.
### 2.2 Content Deployment (Workflow Files)
After Bicep deploys infrastructure, deploy workflow content to the `WEBSITE_CONTENTSHARE` Azure Files share under `site/wwwroot/`:
- **Use `az storage file upload`** to the content share — NOT `az webapp deploy` or zip deploy. This gives atomic control over each file.
- Upload: `host.json`, `connections.json`, `{workflow-name}/workflow.json`, `lib/custom/**`
- Do NOT upload: `local.settings.json`, `.vscode/`, `workflow-designtime/`, `.azurite/`
- Do NOT create nested `site/wwwroot/site/wwwroot/` paths.
- After upload, restart with `az logicapp restart` (NOT `az webapp restart`).
### 2.3 Deployment Script Safety
- App settings with SAS URLs are applied from a JSON file or Bicep parameters, not inline shell arguments.
- Exclude the `.azurite/` folder from any content upload or publish step.
- Runtime mount path in app settings matches `connections.json`.
- File System mount path must not overlap with `site/wwwroot` or any parent `/home` content path.
- The deployment path does not depend on portal-only manual steps.
---
## 2.5 Reference Bicep Template
Use this as the starting point for every Logic App Standard cloud deployment. Adapt parameters as needed.
```bicep
@description('Name of the Logic App Standard resource')
param logicAppName string
@description('Name of the App Service Plan')
param appServicePlanName string
@description('Name of the Storage Account (3-24 chars, lowercase, no hyphens)')
param storageAccountName string
@description('Location for all resources')
param location string = resourceGroup().location
// ─── OPTIONAL: Integration Account (only if EDI/B2B actions are used) ───
@description('Integration Account resource ID (empty string if not used)')
param integrationAccountId string = ''
@description('Integration Account callback URL (empty string if not used)')
@secure()
param integrationAccountCallbackUrl string = ''
// ─── OPTIONAL: File System mount (only if FileSystem service provider is used) ───
@description('File system mount path — MUST be under /mounts/ for Windows')
param fileSystemMountPath string = '/mounts/filesystem'
@description('Azure Files share name for file system connector')
param fileShareName string = 'app-files'
// ─── Storage Account ───
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
properties: {
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
}
}
// ─── Azure Files share (for FileSystem connector — omit if not needed) ───
resource fileService 'Microsoft.Storage/storageAccounts/fileServices@2023-01-01' = {
parent: storageAccount
name: 'default'
}
resource fileShare 'Microsoft.Storage/storageAccounts/fileServices/shares@2023-01-01' = {
parent: fileService
name: fileShareName
properties: { shareQuota: 5 }
}
// ─── App Service Plan (WS1) ───
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
name: appServicePlanName
location: location
sku: {
tier: 'WorkflowStandard'
name: 'WS1'
}
kind: 'elastic'
properties: { elasticScaleEnabled: false }
}
// ─── Logic App Standard ───
resource logicApp 'Microsoft.Web/sites@2023-01-01' = {
name: logicAppName
location: location
kind: 'functionapp,workflowapp'
identity: { type: 'SystemAssigned' }
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
siteConfig: {
netFrameworkVersion: 'v8.0'
use32BitWorkerProcess: false
appSettings: [
// ── Required runtime settings ──
{ name: 'AzureWebJobsStorage', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net' }
{ name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net' }
{ name: 'WEBSITE_CONTENTSHARE', value: logicAppName }
{ name: 'FUNCTIONS_WORKER_RUNTIME', value: 'dotnet' } // Same as local.settings.json
{ name: 'FUNCTIONS_INPROC_NET8_ENABLED', value: '1' } // Required for .NET 8 custom code
{ name: 'APP_KIND', value: 'workflowapp' } // MUST be 'workflowapp'
{ name: 'AzureWebJobsFeatureFlags', value: 'EnableMultiLanguageWorker' }
{ name: 'WEBSITE_NODE_DEFAULT_VERSION', value: '~18' }
{ name: 'FUNCTIONS_EXTENSION_VERSION', value: '~4' } // MUST be '~4'
// ── Workflow management settings ──
{ name: 'WORKFLOWS_SUBSCRIPTION_ID', value: subscription().subscriptionId }
{ name: 'WORKFLOWS_TENANT_ID', value: subscription().tenantId }
{ name: 'WORKFLOWS_RESOURCE_GROUP_NAME', value: resourceGroup().name }
{ name: 'WORKFLOWS_LOCATION_NAME', value: location }
{ name: 'WORKFLOWS_MANAGEMENT_BASE_URI', value: environment().resourceManager }
// ── Integration Account (omit both if not used) ──
{ name: 'WORKFLOWS_INTEGRATION_ACCOUNT_ID', value: integrationAccountId }
{ name: 'WORKFLOW_INTEGRATION_ACCOUNT_CALLBACK_URL', value: integrationAccountCallbackUrl }
// ── FileSystem connector (omit if not used) ──
{ name: 'FileSystem_mountPath', value: fileSystemMountPath }
]
// ── Azure Files mount (omit entire block if FileSystem connector not used) ──
azureStorageAccounts: {
appfiles: {
type: 'AzureFiles'
accountName: storageAccount.name
shareName: fileShareName
mountPath: fileSystemMountPath // MUST match FileSystem_mountPath
accessKey: storageAccount.listKeys().keys[0].value
}
}
}
}
}
output logicAppName string = logicApp.name
output logicAppDefaultHostName string = logicApp.properties.defaultHostName
output storageAccountName string = storageAccount.name
output logicAppResourceId string = logicApp.id
```
### 2.6 Reference Parameters File
Put the callback URL here — NOT in shell arguments — because it contains `&` characters.
```json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"logicAppName": { "value": "la-my-app" },
"appServicePlanName": { "value": "asp-my-app" },
"storageAccountName": { "value": "stmyapp" },
"location": { "value": "eastus" },
"integrationAccountId": {
"value": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Logic/integrationAccounts/{name}"
},
"integrationAccountCallbackUrl": {
"value": "https://prod-XX.eastus.logic.azure.com:443/integrationAccounts/...?api-version=2015-08-01-preview&sp=%2F%2F%2A&sv=1.0&sig=..."
},
"fileSystemMountPath": { "value": "/mounts/edi-files" },
"fileShareName": { "value": "edi-files" }
}
}
```
### 2.7 Reference Content Deployment Script (PowerShell)
Upload workflow content to the WEBSITE_CONTENTSHARE Azure Files share after Bicep deployment.
```powershell
param(
[string]$ResourceGroup,
[string]$StorageAccountName,
[string]$LogicAppName, # Also the content share name
[string]$SourceDir # Path to the logic app project folder
)
# Get storage key
$key = az storage account keys list --account-name $StorageAccountName `
--resource-group $ResourceGroup --query "[0].value" -o tsv
$share = $LogicAppName # WEBSITE_CONTENTSHARE = logic app name
# Create directories
$dirs = @(
"site/wwwroot"
# Add workflow and lib dirs as needed:
# "site/wwwroot/my-workflow"
# "site/wwwroot/lib/custom/net8"
# "site/wwwroot/lib/custom/MyFunction"
)
foreach ($d in $dirs) {
az storage directory create --share-name $share --name $d `
--account-name $StorageAccountName --account-key $key 2>$null
}
# Upload files (recursive helper)
function Upload-File($localPath, $sharePath) {
az storage file upload --share-name $share --source $localPath `
--path $sharePath --account-name $StorageAccountName `
--account-key $key --no-progress 2>$null
}
# Upload core files
Upload-File "$SourceDir\host.json" "site/wwwroot/host.json"
Upload-File "$SourceDir\connections.json" "site/wwwroot/connections.json"
# Upload workflow(s)
Get-ChildItem "$SourceDir" -Directory | Where-Object {
Test-Path "$($_.FullName)\workflow.json"
} | ForEach-Object {
$wfName = $_.Name
az storage directory create --share-name $share `
--name "site/wwwroot/$wfName" `
--account-name $StorageAccountName --account-key $key 2>$null
Upload-File "$($_.FullName)\workflow.json" "site/wwwroot/$wfName/workflow.json"
}
# Upload lib/custom (function DLLs + function.json)
if (Test-Path "$SourceDir\lib\custom") {
Get-ChildItem "$SourceDir\lib\custom" -Recurse -File | ForEach-Object {
$rel = $_.FullName.Substring("$SourceDir\".Length).Replace('\', '/')
$parentDir = "site/wwwroot/" + ($rel -replace '/[^/]+$', '')
az storage directory create --share-name $share --name $parentDir `
--account-name $StorageAccountName --account-key $key 2>$null
Upload-File $_.FullName "site/wwwroot/$rel"
}
}
# Restart
az logicapp restart --name $LogicAppName --resource-group $ResourceGroup
Write-Host "Deployment complete. Wait ~60s for warmup."
```
> **Do NOT upload**: `local.settings.json`, `.vscode/`, `workflow-designtime/`, `.azurite/`, `.gitignore`, `.funcignore`
---
## 3. Cloud Testing
1. Read `TEST-REPORT.md` and run EVERY test scenario from it against the deployed Azure Logic App — happy path, error path, cross-workflow chain, timeout, resubmission — all of them, not a subset.
2. If `BLACKBOX-TEST-REPORT.md` exists in the project root (generated by the local black box testing task), read it and re-execute ALL test cases from that report against the deployed Azure Logic App as well. Include those results in `CLOUD-TEST-REPORT.md`.
3. Fix any cloud-specific issues via `az webapp config appsettings set` — NEVER edit `local.settings.json`.
4. If the workflow uses File System triggers or actions, verify the Azure-side File System connection/authentication works in cloud execution, not just local mounted execution.
5. After all tests pass, UPDATE the ARM/Bicep template to include ALL changes made during testing so the template alone can reproduce the working deployment from scratch.
### 3.1 Querying Workflow Run History
Use API version `2024-11-01` (NOT `2024-02-02` which is unsupported for the hostruntime API):
```
GET https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/sites/{app}/hostruntime/runtime/webhooks/workflow/api/management/workflows/{workflow}/runs?api-version=2024-11-01
```
With `az rest`:
```bash
az rest --method GET \
--url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/sites/{app}/hostruntime/runtime/webhooks/workflow/api/management/workflows/{workflow}/runs?api-version=2024-11-01" \
--resource "https://management.azure.com"
```
To get action details for a specific run:
```bash
az rest --method GET \
Auf GitHub ansehen