Consumption Logic App workflow shells with typed parameters, shared API connections, access control, and the hidden-title tag, per the Libre DevOps Logic App standard.
The module owns the workflow SHELL; content (triggers and actions) is authored as raw
azurerm_logic_app_trigger_* / azurerm_logic_app_action_* resources against the module's ids,
per the Libre DevOps Logic App standard:
content graphs are per-playbook and separate resources give explicit depends_on ordering.
What the shell earns:
- Typed parameters, the no-hardcoding path: one entry generates both the workflow parameter
definition (type, defaultValue, allowedValues, description) and its deployment value; content
only ever says
@parameters('name'). Secure types cannot carry defaults or allowed values (definitions are Reader-visible), every parameter must have a value or default, and values are type-checked at plan time. - Connections declared once:
shared_connectionsputs the estate's common API connections (the usual Sentinel/Monitor/ITSM set) on every workflow; per-workflowconnectionsmerge over them by key, anduse_shared_connections = falseopts out. The module generates the entire$connectionsparameter pair, including theManagedServiceIdentityauthentication block, byte compatible with@parameters('$connections')['<api>']['connectionId']references. - Diagnostics declared once:
diagnosticsgives every workflow an allLogs diagnostic setting nameddiag-<workflow>, with per-workflow override and opt-out. - The hidden-title tag is required:
titlebecomes the portal subtitle, as the standard mandates. - Access control first-class: per-plane IP restrictions and AAD open authentication policies
on the trigger; a
checkflags workflows whose triggers are SAS-only.
The retired Integration Service Environment is deliberately not exposed. The resource group is passed by id and parsed.
locals {
# The playbook catalog: one entry per workflow shell; content lives in per-playbook files.
playbooks = {
"logic-ldo-uks-prd-001" = {
title = "Sentinel Incident - MDO incidents to ServiceNow"
}
"logic-ldo-uks-prd-002" = {
title = "HTTP - Summarise a Sentinel alert storm and notify on-call"
parameters = {
storm_threshold = { type = "Int", value = var.storm_threshold }
}
}
}
}
module "playbooks" {
source = "libre-devops/logic-app-workflow/azurerm"
version = "~> 4.0"
resource_group_id = module.rg.ids["rg-ldo-uks-prd-001"]
location = "uksouth"
tags = module.tags.tags
shared_connections = {
"azuresentinel" = {
connection_id = azapi_resource.sentinel_connection.id
managed_api_id = data.azurerm_managed_api.sentinel.id
managed_identity_auth = true
}
}
diagnostics = { log_analytics_workspace_id = module.law.workspace_ids["log-ldo-uks-prd-001"] }
workflows = local.playbooks
}examples/minimal- one shell plus the standard's content shape: a recurrence trigger and a Compose action proving@parameters()end to end.examples/complete- the alert-storm playbook: a Sentinel-onboarded workspace, our own Sentinel API connection (azapi, managed identity auth) as the shared estate default, a scheduled query rule counting incident volume, an action group whose logic app receiver fires the workflow's HTTP trigger, and content with verbose names, descriptions on every action, an explicit exponential retry policy, and a Scope try/catch.
Local work needs PowerShell 7+ and just, because the recipes
wrap the LibreDevOpsHelpers
PowerShell module (the same engine the libre-devops/terraform-azure action runs in CI). Install
just with brew install just, or uv tool add rust-just then uv run just <recipe>.
Run just to list recipes: just update-ldo-pwsh (install or force-update LibreDevOpsHelpers from
PSGallery), just validate, just scan (Trivy only), just pwsh-analyze (PSScriptAnalyzer only),
just plan, just apply, just destroy, just e2e, just test, and just docs (the
plan/apply/destroy recipes mirror the action, including the storage firewall dance; just e2e
applies an example then always destroys it, defaulting to minimal, so nothing is left running).
Releasing is also just:
just increment-release [patch|minor|major] bumps, tags, and publishes a GitHub release, and the
Terraform Registry picks up the tag.
This module is scanned with Trivy; HIGH and CRITICAL
findings fail the build. Any waiver is a deliberate, reviewed decision, never a way to quiet a
finding that should be fixed. Waivers live in .trivyignore.yaml (the
machine-applied source of truth, passed to Trivy with --ignorefile) and are mirrored in the table
below so the reason is auditable.
| Trivy ID | Resource | Finding | Justification |
|---|---|---|---|
| None |
To add an exception: add an entry to .trivyignore.yaml (id, optional paths to scope it, and a
statement recording why), then add a matching row here. Where the finding is out of this module's
scope, point the justification at the Libre DevOps module that does address it (for example the
private-endpoint module). Both the file and this table are reviewed in the pull request.
The Requirements, Providers, Inputs, Outputs, and Resources below are generated by terraform-docs.
| Name | Version |
|---|---|
| terraform | >= 1.9.0, < 2.0.0 |
| azurerm | >= 4.0.0, < 5.0.0 |
| Name | Version |
|---|---|
| azurerm | >= 4.0.0, < 5.0.0 |
No modules.
| Name | Type |
|---|---|
| azurerm_logic_app_workflow.this | resource |
| azurerm_monitor_diagnostic_setting.this | resource |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| diagnostics | Module-level default diagnostics: when set, every workflow gets an allLogs diagnostic setting to this workspace (named diag-) unless it sets its own diagnostics or opts out with diagnostics_enabled = false. An object rather than a bare string so its presence stays plan-known when the workspace is created in the same apply (for_each keys must never depend on unknown values). | object({ |
null |
no |
| location | Azure region for the workflows. | string |
n/a | yes |
| resource_group_id | Resource id of the resource group the workflows are created in. The resource group name and subscription are parsed from this id. | string |
n/a | yes |
| shared_connections | API connections shared by every workflow in the call (the usual estate shape: the same Sentinel/Monitor/ITSM connections on every playbook), keyed by managed API name. Merged under each workflow's own connections (same-key workflow entries win); a workflow opts out with use_shared_connections = false. | map(object({ |
{} |
no |
| tags | Tags applied to the workflows (merged with per-workflow tags; the workflow's title always lands as the hidden-title tag). | map(string) |
{} |
no |
| workflows | Consumption Logic App workflows (Microsoft.Logic/workflows) keyed by name (logic-ldo-uks-prd-001). The workflow content (triggers and actions) is authored as raw azurerm_logic_app_trigger_* / azurerm_logic_app_action_* resources per the Libre DevOps Logic App standard; this module owns the shell. Fields: title REQUIRED human-readable description, becomes the hidden-title tag the portal renders as the resource subtitle ("{Trigger type} - {what it does}"). parameters Typed workflow parameters, the no-hardcoding path. Each entry generates BOTH the workflow parameter definition (type, defaultValue, description, allowedValues) AND its deployment value, so content only ever references @parameters('name'). Types: String, SecureString, Int, Float, Bool, Object, Array, SecureObject. Values are strings (Terraform coerces numbers and bools, so value = 25 works; Object and Array values pass jsonencode(...), the same rule the standard sets for all workflow JSON); the module converts defaults and allowed values back to their real JSON types in the definition. Secure types must not set default or allowed_values (their definitions are readable by anyone with Reader). identity SystemAssigned by default; UserAssigned supported. connections API connections for the workflow's managed connectors, keyed by the managed API name the action bodies reference. Generates the entire $connections parameter pair (definition and value: connectionId, connectionName, id, and the ManagedServiceIdentity authentication block when managed_identity_auth is true), so bodies keep saying @parameters('$connections')['']['connectionId'] with zero hand-rolled JSON. diagnostics Optional per-workflow diagnostic setting (allLogs) to a Log Analytics workspace, named diag- unless overridden. access_control IP restrictions for action/content/workflow_management, and for the trigger additionally AAD open authentication policies (claims like iss/aud/appid), the right way to let an action group or app call an HTTP trigger without shared SAS exposure. enabled, workflow_schema, workflow_version, integration_account_id Pass-throughs. The retired Integration Service Environment (integration_service_environment_id) is deliberately not exposed. |
map(object({ |
{} |
no |
| Name | Description |
|---|---|
| access_endpoints | Map of workflow name to its access endpoint. |
| connector_outbound_ip_addresses | Map of workflow name to the outbound IPs its managed connectors call from (for allow-listing on downstream firewalls). |
| identities | Map of workflow name to its identity { principal_id, tenant_id } (principal_id is populated for system-assigned identities), for role assignments. |
| ids | Map of workflow name to its resource id. |
| ids_zipmap | Map of workflow name to a { name, id } object, for passing where both are needed together. |
| names | The workflow names. |
| resource_group_name | Resource group name parsed from resource_group_id. |
| subscription_id | Subscription id parsed from resource_group_id. |
| tags | The base tags applied to the workflows. |
| workflow_outbound_ip_addresses | Map of workflow name to the outbound IPs the workflow runtime calls from (for allow-listing on downstream firewalls). |