diff --git a/README.md b/README.md index fbcca79..3d250b4 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ No modules. | [container\_app\_environment\_name](#input\_container\_app\_environment\_name) | (Required) The name of the container apps managed environment. Changing this forces a new resource to be created. | `string` | n/a | yes | | [container\_app\_environment\_tags](#input\_container\_app\_environment\_tags) | A map of the tags to use on the resources that are deployed with this module. | `map(string)` | `{}` | no | | [container\_app\_secrets](#input\_container\_app\_secrets) | (Optional) The secrets of the container apps. The key of the map should be aligned with the corresponding container app. |
map(list(object({
name = string
value = string
})))
| `{}` | no | -| [container\_apps](#input\_container\_apps) | The container apps to deploy. |
map(object({
name = string
tags = optional(map(string))
revision_mode = string
workload_profile_name = optional(string)

template = object({
init_containers = optional(set(object({
args = optional(list(string))
command = optional(list(string))
cpu = optional(number)
image = string
name = string
memory = optional(string)
env = optional(list(object({
name = string
secret_name = optional(string)
value = optional(string)
})))
volume_mounts = optional(list(object({
name = string
path = string
})))
})), [])
containers = set(object({
name = string
image = string
args = optional(list(string))
command = optional(list(string))
cpu = string
memory = string
env = optional(set(object({
name = string
secret_name = optional(string)
value = optional(string)
})))
liveness_probe = optional(object({
failure_count_threshold = optional(number)
header = optional(object({
name = string
value = string
}))
host = optional(string)
initial_delay = optional(number, 1)
interval_seconds = optional(number, 10)
path = optional(string)
port = number
timeout = optional(number, 1)
transport = string
}))
readiness_probe = optional(object({
failure_count_threshold = optional(number)
header = optional(object({
name = string
value = string
}))
host = optional(string)
interval_seconds = optional(number, 10)
path = optional(string)
port = number
success_count_threshold = optional(number, 3)
timeout = optional(number)
transport = string
}))
startup_probe = optional(object({
failure_count_threshold = optional(number)
header = optional(object({
name = string
value = string
}))
host = optional(string)
interval_seconds = optional(number, 10)
path = optional(string)
port = number
timeout = optional(number)
transport = string
}))
volume_mounts = optional(list(object({
name = string
path = string
})))
}))
max_replicas = optional(number)
min_replicas = optional(number)
revision_suffix = optional(string)

volume = optional(set(object({
name = string
storage_name = optional(string)
storage_type = optional(string)
})))
})

ingress = optional(object({
allow_insecure_connections = optional(bool, false)
external_enabled = optional(bool, false)
ip_security_restrictions = optional(list(object({
action = string
ip_address_range = string
name = string
description = optional(string)
})), [])
target_port = number
transport = optional(string)
traffic_weight = object({
label = optional(string)
latest_revision = optional(string)
revision_suffix = optional(string)
percentage = number
})
}))

identity = optional(object({
type = string
identity_ids = optional(list(string))
}))

dapr = optional(object({
app_id = string
app_port = number
app_protocol = optional(string)
}))

registry = optional(list(object({
server = string
username = optional(string)
password_secret_name = optional(string)
identity = optional(string)
})))
}))
| n/a | yes | +| [container\_apps](#input\_container\_apps) | The container apps to deploy. |
map(object({
name = string
tags = optional(map(string))
revision_mode = string
workload_profile_name = optional(string)

template = object({
init_containers = optional(set(object({
args = optional(list(string))
command = optional(list(string))
cpu = optional(number)
image = string
name = string
memory = optional(string)
env = optional(list(object({
name = string
secret_name = optional(string)
value = optional(string)
})))
volume_mounts = optional(list(object({
name = string
path = string
})))
})), [])
containers = set(object({
name = string
image = string
args = optional(list(string))
command = optional(list(string))
cpu = string
memory = string
env = optional(set(object({
name = string
secret_name = optional(string)
value = optional(string)
})))
liveness_probe = optional(object({
failure_count_threshold = optional(number)
header = optional(object({
name = string
value = string
}))
host = optional(string)
initial_delay = optional(number, 1)
interval_seconds = optional(number, 10)
path = optional(string)
port = number
timeout = optional(number, 1)
transport = string
}))
readiness_probe = optional(object({
failure_count_threshold = optional(number)
header = optional(object({
name = string
value = string
}))
host = optional(string)
interval_seconds = optional(number, 10)
path = optional(string)
port = number
success_count_threshold = optional(number, 3)
timeout = optional(number)
transport = string
}))
startup_probe = optional(object({
failure_count_threshold = optional(number)
header = optional(object({
name = string
value = string
}))
host = optional(string)
interval_seconds = optional(number, 10)
path = optional(string)
port = number
timeout = optional(number)
transport = string
}))
volume_mounts = optional(list(object({
name = string
path = string
})))
}))
max_replicas = optional(number)
min_replicas = optional(number)
revision_suffix = optional(string)
http_scale_rules = optional(list(object({
name = string
concurrent_requests = number
authentication = optional(object({
secret_name = string
trigger_parameter = string
}))
})))
custom_scale_rules = optional(list(object({
name = string
custom_rule_type = string # See https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/container_app#custom_rule_type
metadata = map(string)
authentication = optional(object({
secret_name = string
trigger_parameter = string
}))
})))
volume = optional(set(object({
name = string
storage_name = optional(string)
storage_type = optional(string)
})))
})

ingress = optional(object({
allow_insecure_connections = optional(bool, false)
external_enabled = optional(bool, false)
ip_security_restrictions = optional(list(object({
action = string
ip_address_range = string
name = string
description = optional(string)
})), [])
target_port = number
transport = optional(string)
traffic_weight = object({
label = optional(string)
latest_revision = optional(string)
revision_suffix = optional(string)
percentage = number
})
}))

identity = optional(object({
type = string
identity_ids = optional(list(string))
}))

dapr = optional(object({
app_id = string
app_port = number
app_protocol = optional(string)
}))

registry = optional(list(object({
server = string
username = optional(string)
password_secret_name = optional(string)
identity = optional(string)
})))

}))
| n/a | yes | | [dapr\_component](#input\_dapr\_component) | (Optional) The Dapr component to deploy. |
map(object({
name = string
component_type = string
version = string
ignore_errors = optional(bool, false)
init_timeout = optional(string, "5s")
scopes = optional(list(string))
metadata = optional(set(object({
name = string
secret_name = optional(string)
value = string
})))
}))
| `{}` | no | | [dapr\_component\_secrets](#input\_dapr\_component\_secrets) | (Optional) The secrets of the Dapr components. The key of the map should be aligned with the corresponding Dapr component. |
map(list(object({
name = string
value = string
})))
| `{}` | no | | [env\_storage](#input\_env\_storage) | (Optional) Manages a Container App Environment Storage, writing files to this file share to make data accessible by other systems. |
map(object({
name = string
account_name = string
share_name = string
access_mode = string
}))
| `{}` | no | diff --git a/main.tf b/main.tf index 1cf4493..1b4b9c9 100644 --- a/main.tf +++ b/main.tf @@ -263,6 +263,36 @@ resource "azurerm_container_app" "container_app" { storage_type = volume.value.storage_type } } + dynamic "http_scale_rule" { + for_each = each.value.template.http_scale_rules + content { + name = http_scale_rule.value.name + concurrent_requests = http_scale_rule.value.concurrent_requests + dynamic "authentication" { + for_each = http_scale_rule.value.authentication != null ? [http_scale_rule.value.authentication] : [] + content { + secret_name = authentication.value.secret_name + trigger_parameter = authentication.value.trigger_parameter + } + } + } + } + + dynamic "custom_scale_rule" { + for_each = each.value.template.custom_scale_rules + content { + name = custom_scale_rule.value.name + custom_rule_type = custom_scale_rule.value.custom_rule_type + metadata = custom_scale_rule.value.metadata + dynamic "authentication" { + for_each = custom_scale_rule.value.authentication != null ? [custom_scale_rule.value.authentication] : [] + content { + secret_name = authentication.value.secret_name + trigger_parameter = authentication.value.trigger_parameter + } + } + } + } } dynamic "dapr" { for_each = each.value.dapr == null ? [] : [each.value.dapr] @@ -330,4 +360,5 @@ resource "azurerm_container_app" "container_app" { value = local.container_app_secrets[each.key][secret.key] } } + } diff --git a/variables.tf b/variables.tf index c9b3443..f09a99b 100644 --- a/variables.tf +++ b/variables.tf @@ -90,7 +90,23 @@ variable "container_apps" { max_replicas = optional(number) min_replicas = optional(number) revision_suffix = optional(string) - + http_scale_rules = optional(list(object({ + name = string + concurrent_requests = number + authentication = optional(object({ + secret_name = string + trigger_parameter = string + })) + }))) + custom_scale_rules = optional(list(object({ + name = string + custom_rule_type = string # See https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/container_app#custom_rule_type + metadata = map(string) + authentication = optional(object({ + secret_name = string + trigger_parameter = string + })) + }))) volume = optional(set(object({ name = string storage_name = optional(string) @@ -134,6 +150,7 @@ variable "container_apps" { password_secret_name = optional(string) identity = optional(string) }))) + })) description = "The container apps to deploy." nullable = false @@ -146,6 +163,14 @@ variable "container_apps" { condition = alltrue([for n, c in var.container_apps : c.ingress == null ? true : (c.ingress.ip_security_restrictions == null ? true : (length(distinct([for r in c.ingress.ip_security_restrictions : r.action])) <= 1))]) error_message = "The `action` types in an all `ip_security_restriction` blocks must be the same for the `ingress`, mixing `Allow` and `Deny` rules is not currently supported by the service." } + validation { + condition = alltrue([for n, c in var.container_apps : c.template.custom_scale_rules == null || alltrue([for _, r in c.template.custom_scale_rules : can(regex("^[a-z0-9][a-z0-9-.]*[a-z0-9]$", r.name))])]) + error_message = "The `name` in `custom_scale_rule` must consist of lower case alphanumeric characters, '-', or '.', and should start and end with an alphanumeric character." + } + validation { + condition = alltrue([for n, c in var.container_apps : c.template.http_scale_rules == null || alltrue([for _, r in c.template.http_scale_rules : can(regex("^[a-z0-9][a-z0-9-.]*[a-z0-9]$", r.name))])]) + error_message = "The `name` in `http_scale_rule` must consist of lower case alphanumeric characters, '-', or '.', and should start and end with an alphanumeric character." + } } variable "location" {