Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions infrastructure/modules/logic-app-slack-alert/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# logic-app-slack-alert

Deploy an [Azure Logic App](https://learn.microsoft.com/en-us/azure/logic-apps/logic-apps-overview) that receives [Azure Monitor alert notifications](https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/action-groups) via HTTP and forwards them to a Slack channel as formatted messages.

## Terraform documentation

For the list of inputs, outputs, resources... check the [terraform module documentation](tfdocs.md).

## Prerequisites

- A Slack incoming webhook URL. Create one via **Slack App > Incoming Webhooks** and store it in Azure Key Vault. Pass the resolved secret value as `slack_webhook_url`.
- An [Azure Monitor action group](https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/action-groups) to register the Logic App trigger URL with as a webhook receiver. Use the `trigger_callback_url` output for this.

## Usage

```hcl
module "logic_app_slack_alert" {
source = "../../../dtos-devops-templates/infrastructure/modules/logic-app-slack-alert"

name = "logic-slack-alert-myapp-dev-uks"
resource_group_name = azurerm_resource_group.main.name
location = "uksouth"
slack_webhook_url = data.azurerm_key_vault_secret.slack_webhook.value
}

resource "azurerm_monitor_action_group" "slack" {
name = "ag-slack-myapp-dev-uks"
resource_group_name = azurerm_resource_group.main.name
short_name = "slack"

webhook_receiver {
name = "logic-app-slack"
service_uri = module.logic_app_slack_alert.trigger_callback_url
use_common_alert_schema = true
}
}
```

Then attach the action group to any Azure Monitor alert rule:

```hcl
resource "azurerm_monitor_metric_alert" "example" {
# ...
action {
action_group_id = azurerm_monitor_action_group.slack.id
}
}
```

## Slack message format

Each alert posts a Block Kit message to the configured channel containing:

| Field | Source |
|-------|--------|
| Header | Alert rule name, prefixed with 🚨 (fired) or ✅ (resolved) |
| Severity | `Sev0`–`Sev4` from the alert essentials |
| Status | `Fired` or `Resolved` |
| Fired At | UTC timestamp from the alert payload |
| Resource | First configuration item (affected resource name) |
| Description | Alert rule description |
| Link | Deep link to the Failures blade (App Insights alerts) or the resource overview (all other alert types) |

## Common alert schema

The Logic App trigger is configured to accept the [Azure Monitor common alert schema](https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/alerts-common-schema). Ensure `use_common_alert_schema = true` is set on the action group webhook receiver (as shown in the usage example above), otherwise the trigger will reject the payload.

## Webhook URL security

The `trigger_callback_url` output is marked `sensitive`. It contains a SAS token that grants the ability to trigger the Logic App — treat it like a secret. Azure regenerates this URL if the Logic App is recreated.

The `slack_webhook_url` is stored as a `SecureString` workflow parameter in the Logic App, meaning it does not appear in the run history or trigger inputs in the Azure portal.
122 changes: 122 additions & 0 deletions infrastructure/modules/logic-app-slack-alert/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
resource "azurerm_logic_app_workflow" "this" {
name = var.name
resource_group_name = var.resource_group_name
location = var.location

workflow_parameters = {
"slackWebhookUrl" = jsonencode({
type = "SecureString"
defaultValue = ""
})
}

parameters = {
"slackWebhookUrl" = var.slack_webhook_url
}
}

resource "azurerm_logic_app_trigger_http_request" "this" {
name = "When_an_HTTP_request_is_received"
logic_app_id = azurerm_logic_app_workflow.this.id
method = "POST"

schema = jsonencode({
"$schema" = "http://json-schema.org/draft-04/schema#"
type = "object"
properties = {
schemaId = { type = "string" }
data = {
type = "object"
properties = {
essentials = {
type = "object"
properties = {
alertRule = { type = "string" }
severity = { type = "string" }
firedDateTime = { type = "string" }
resolvedDateTime = { type = "string" }
monitorCondition = { type = "string" }
description = { type = "string" }
alertTargetIDs = {
type = "array"
items = { type = "string" }
}
configurationItems = {
type = "array"
items = { type = "string" }
}
}
}
}
}
}
})
}

resource "azurerm_logic_app_action_custom" "post_to_slack" {
name = "Post_to_Slack"
logic_app_id = azurerm_logic_app_workflow.this.id

body = <<-BODY
{
"type": "Http",
"inputs": {
"method": "POST",
"uri": "@parameters('slackWebhookUrl')",
"headers": {
"Content-Type": "application/json"
},
"body": {
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "@{if(equals(triggerBody()?['data']?['essentials']?['monitorCondition'], 'Resolved'), concat('✅ Resolved – ', triggerBody()?['data']?['essentials']?['alertRule']), concat('🚨 Alert – ', triggerBody()?['data']?['essentials']?['alertRule']))}",
"emoji": true
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "@{concat('*Severity*\n', triggerBody()?['data']?['essentials']?['severity'])}"
},
{
"type": "mrkdwn",
"text": "@{concat('*Status*\n', triggerBody()?['data']?['essentials']?['monitorCondition'])}"
},
{
"type": "mrkdwn",
"text": "@{if(equals(triggerBody()?['data']?['essentials']?['monitorCondition'], 'Resolved'), concat('*Resolved At*\n', triggerBody()?['data']?['essentials']?['resolvedDateTime']), concat('*Fired At*\n', triggerBody()?['data']?['essentials']?['firedDateTime']))}"
},
{
"type": "mrkdwn",
"text": "@{concat('*Resource*\n`', coalesce(triggerBody()?['data']?['essentials']?['configurationItems']?[0], 'N/A'), '`')}"
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "@{concat('*Description*\n> ', coalesce(triggerBody()?['data']?['essentials']?['description'], 'N/A'))}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "@{if(contains(coalesce(triggerBody()?['data']?['essentials']?['alertTargetIDs']?[0], ''), 'microsoft.insights/components'), concat(':mag: <https://portal.azure.com/#resource', triggerBody()?['data']?['essentials']?['alertTargetIDs']?[0], '/failures|View Failures in App Insights>'), concat(':mag: <https://portal.azure.com/#resource', coalesce(triggerBody()?['data']?['essentials']?['alertTargetIDs']?[0], ''), '|View Resource in Azure Portal>'))}"
}
}
]
}
},
"runAfter": {}
}
BODY

depends_on = [azurerm_logic_app_trigger_http_request.this]
}
5 changes: 5 additions & 0 deletions infrastructure/modules/logic-app-slack-alert/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
output "trigger_callback_url" {
description = "HTTP trigger callback URL to register with an Azure Monitor action group webhook receiver."
value = azurerm_logic_app_trigger_http_request.this.callback_url
sensitive = true
}
44 changes: 44 additions & 0 deletions infrastructure/modules/logic-app-slack-alert/tfdocs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Module documentation

## Required Inputs

The following input variables are required:

### <a name="input_location"></a> [location](#input\_location)

Description: Azure region in which to create the Logic App.

Type: `string`

### <a name="input_name"></a> [name](#input\_name)

Description: Name of the Logic App workflow.

Type: `string`

### <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name)

Description: Resource group in which to create the Logic App.

Type: `string`

### <a name="input_slack_webhook_url"></a> [slack\_webhook\_url](#input\_slack\_webhook\_url)

Description: Slack incoming webhook URL. Stored as a SecureString workflow parameter.

Type: `string`

## Outputs

The following outputs are exported:

### <a name="output_trigger_callback_url"></a> [trigger\_callback\_url](#output\_trigger\_callback\_url)

Description: HTTP trigger callback URL to register with an Azure Monitor action group webhook receiver.
## Resources

The following resources are used by this module:

- [azurerm_logic_app_action_custom.post_to_slack](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/logic_app_action_custom) (resource)
- [azurerm_logic_app_trigger_http_request.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/logic_app_trigger_http_request) (resource)
- [azurerm_logic_app_workflow.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/logic_app_workflow) (resource)
20 changes: 20 additions & 0 deletions infrastructure/modules/logic-app-slack-alert/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
variable "name" {
type = string
description = "Name of the Logic App workflow."
}

variable "resource_group_name" {
type = string
description = "Resource group in which to create the Logic App."
}

variable "location" {
type = string
description = "Azure region in which to create the Logic App."
}

variable "slack_webhook_url" {
type = string
description = "Slack incoming webhook URL. Stored as a SecureString workflow parameter."
sensitive = true
}
9 changes: 9 additions & 0 deletions infrastructure/modules/virtual-desktop/tfdocs.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ Type: `number`

Default: `16`

### <a name="input_principal_id"></a> [principal\_id](#input\_principal\_id)

Description: The principal (object) ID to assign the 'Desktop Virtualization Power On Off Contributor' role to the host pool. If null, the role assignment will not be created. This maintains backward compatibility for existing deployments. The role is required for autoscaling but can be omitted if autoscaling is not used or the role is assigned manually.

Type: `string`

Default: `null`

### <a name="input_source_image_from_gallery"></a> [source\_image\_from\_gallery](#input\_source\_image\_from\_gallery)

Description: n/a
Expand Down Expand Up @@ -279,6 +287,7 @@ Default: `null`
The following resources are used by this module:

- [azurerm_network_interface.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface) (resource)
- [azurerm_role_assignment.avd_autoscale_hostpool](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) (resource)
- [azurerm_role_assignment.dag_admins](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) (resource)
- [azurerm_role_assignment.dag_users](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) (resource)
- [azurerm_role_assignment.rg_admins](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) (resource)
Expand Down
Loading