Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
resource "azurerm_monitor_metric_alert" "this" {
name = "${var.name}-availability-alert"
resource_group_name = var.resource_group_name

scopes = [azurerm_application_insights_standard_web_test.this.id, var.application_insights_id]
severity = 0

frequency = var.alert.frequency
window_size = var.alert.window_size
auto_mitigate = var.alert.auto_mitigate

application_insights_web_test_location_availability_criteria {
web_test_id = azurerm_application_insights_standard_web_test.this.id
component_id = var.application_insights_id
failed_location_count = var.alert.failed_location_count
}

description = var.alert_description

action {
action_group_id = var.action_group_id
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,37 @@ resource "azurerm_application_insights_standard_web_test" "this" {
enabled = true

request {
url = var.target_url
}
url = var.target_url
http_verb = var.http_verb
body = var.request_body

geo_locations = var.geo_locations
}
dynamic "header" {
for_each = var.headers
content {
name = header.key
value = header.value
}
}
}

resource "azurerm_monitor_metric_alert" "this" {
name = "${var.name}-availability-alert"
resource_group_name = var.resource_group_name
scopes = [azurerm_application_insights_standard_web_test.this.id, var.application_insights_id]
description = "availability test alert"
severity = 0
# Validation rules
dynamic "validation_rules" {
for_each = var.ssl_validation.enabled ? [1] : []
content {
expected_status_code = var.ssl_validation.expected_status_code
ssl_check_enabled = var.ssl_validation.ssl_check_enabled
ssl_cert_remaining_lifetime = var.ssl_validation.ssl_cert_remaining_lifetime

application_insights_web_test_location_availability_criteria {
web_test_id = azurerm_application_insights_standard_web_test.this.id
component_id = var.application_insights_id
failed_location_count = 2
dynamic "content" {
for_each = var.ssl_validation.content == null ? [] : [var.ssl_validation.content]
content {
content_match = content.value.match
ignore_case = try(content.value.ignore_case, true)
pass_if_text_found = try(content.value.pass_if_text_found, true)
}
}
}
}

action {
action_group_id = var.action_group_id
}
geo_locations = var.geo_locations
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ Description: List of Azure test locations (provider-specific location strings fo
Type: `list(string)`

Default:

```json
[
"emea-ru-msa-edge",
Expand All @@ -62,6 +61,9 @@ Default:
]
```

Validations:
- At least one geo location must be provided.

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

Description: The location/region where the availability test is deployed (must match App Insights location)
Expand All @@ -78,6 +80,103 @@ Type: `number`

Default: `30`

### <a name="input_http_verb"></a> [http_verb](#input\_http\_verb)

Description: The HTTP verb used for the request.

Type: `string`

Allowed values: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS

Default: GET

### <a name="input_headers"></a> [headers](#input\_headers)

Description: A map of HTTP request headers (name => value).

Type: `map(string)`

Default: {}

### <a name="input_request_body"></a> [request_body](#input\_request\_body)

Description: Request body to send with the HTTP call. Use jsonencode() for JSON payloads.

Type: `string`

Default: null

### <a name="input_alert_description"></a> [alert_description](#input\_alert\_description)

Description: The description applied to the alert rule.

Type: `string`

Default: "Availability test alert"

### <a name="input_ssl_validation"></a> [ssl_validation](#input\_ssl\_validation)

Description: SSL validation configuration for the availability test. Set `enabled = false` to omit SSL validation completely. To validate response body content, set content.match to a non-null string.

Type:
```hcl
object({
enabled = optional(bool, true)
expected_status_code = optional(number, 200)
ssl_check_enabled = optional(bool, true)
ssl_cert_remaining_lifetime = optional(number, null)
content = optional(object({
match = string
ignore_case = optional(bool, true)
pass_if_text_found = optional(bool, true)
}), null)
})
```

Default:
```hcl
{
enabled = true
expected_status_code = 200
ssl_check_enabled = true
ssl_cert_remaining_lifetime = null
content = null
}
```

Validations:
- expected_status_code must be 0 or a valid HTTP status code (100–599)
- ssl_cert_remaining_lifetime must be null or between 1–365

### <a name="input_alert"></a> [alert](#input\_alert)

Description: Configuration for the availability alert rule.

Type:
```hcl
object({
frequency = optional(string, "PT1H")
window_size = optional(string, "P1D")
auto_mitigate = optional(bool, true)
failed_location_count = optional(number, 2)
})
```

Defaults:
```hcl
{
frequency = "PT1H"
window_size = "P1D"
auto_mitigate = true
}
```

Validations:
- frequency must be one of: PT1M, PT5M, PT15M, PT30M, PT1H
- window_size must be one of: PT1M, PT5M, PT15M, PT30M, PT1H, PT6H, PT12H, P1D
- failed_location_count must be:
>= 1, and
<= the number of configured geo_locations

## Resources

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,138 @@ variable "timeout" {
type = number
default = 30
description = "Timeout in seconds, defaults to 30."
validation {
condition = var.timeout > 0
error_message = "Timeout must be a positive number of seconds."
}
}

variable "geo_locations" {
type = list(string)
default = ["emea-ru-msa-edge", "emea-se-sto-edge", "emea-gb-db3-azr"]
description = "List of Azure test locations (provider-specific location strings for UK and Ireland)"
validation {
condition = length(var.geo_locations) >= 1
error_message = "At least one geo location must be provided."
}
}

variable "target_url" {
type = string
description = "The target URL for the restful endpoint to hit to validate the application is available"
validation {
condition = can(regex("^https?://", var.target_url))
error_message = "The target URL must start with http:// or https://."
}
}

variable "http_verb" {
description = "HTTP verb (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)"
type = string
default = "GET"
validation {
condition = contains(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"], var.http_verb)
error_message = "http_verb must be one of GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS."
}
}

variable "headers" {
description = "Map of request headers to send (name => value)"
type = map(string)
default = {}
}

variable "request_body" {
description = "The request body string; use jsonencode(...) for JSON"
type = string
default = null
}

variable "alert_description" {
description = "The description for alert"
type = string
default = "Availability test alert"
}

variable "ssl_validation" {
description = <<EOT
The SSL validation settings for the standard web test.

Set `enabled = false` to omit the SSL validation block entirely.
If you want to validate response body text, set content's match to a non-null string.
EOT
type = object({
enabled = optional(bool, true)
expected_status_code = optional(number, 200)
ssl_check_enabled = optional(bool, true)
ssl_cert_remaining_lifetime = optional(number, null)

content = optional(object({
match = string
ignore_case = optional(bool, true)
pass_if_text_found = optional(bool, true)
}), null)
})

validation {
condition = (
var.ssl_validation.expected_status_code == 0 ||
(var.ssl_validation.expected_status_code >= 100 &&
var.ssl_validation.expected_status_code < 600)
)
error_message = "The expected status code must be 0 or a valid HTTP status code in [100, 599]."
}

validation {
condition = (
var.ssl_validation.ssl_cert_remaining_lifetime == null ||
(var.ssl_validation.ssl_cert_remaining_lifetime >= 1 &&
var.ssl_validation.ssl_cert_remaining_lifetime <= 365)
)
error_message = "The SSL certificate remaining lifetime must be null or an integer between 1 and 365."
}

default = {
enabled = true
expected_status_code = 200
ssl_check_enabled = true
ssl_cert_remaining_lifetime = null
content = null
}
}

variable "alert" {
type = object({
frequency = optional(string, "PT1H")
window_size = optional(string, "P1D")
auto_mitigate = optional(bool, true)
failed_location_count = optional(number, 2)
})

validation {
condition = contains(
["PT1M", "PT5M", "PT15M", "PT30M", "PT1H"],
var.alert.frequency
)
error_message = "Frequency must be one of: PT1M, PT5M, PT15M, PT30M, PT1H"
}

validation {
condition = contains(
["PT1M", "PT5M", "PT15M", "PT30M", "PT1H", "PT6H", "PT12H", "P1D"],
var.alert.window_size
)
error_message = "Window size must be one of: PT1M, PT5M, PT15M, PT30M, PT1H, PT6H, PT12H, P1D"
}

validation {
condition = var.alert.failed_location_count >= 1 && var.alert.failed_location_count <= length(var.geo_locations)
error_message = "The failed location count must be >= 1 and cannot exceed the number of configured geo locations."
}

default = {
frequency = "PT1H" # every 24 hours
window_size = "P1D" # last 24 hours
auto_mitigate = true # automatically mitigate the alert when thie issue is resolved
}
}