From 72742bbcaf8e8e705bab1201b0942b4031a9a906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garrido=20S=C3=A1nchez?= Date: Sun, 4 Aug 2024 09:28:29 +0000 Subject: [PATCH 1/2] refactor: http listeners --- .../frontend_ip_configuration_parameters.json | 15 ++- .config/http_listener_parameters.json | 2 +- .pre-commit-config.yaml | 4 +- CHANGELOG.md | 1 + README.md | 8 +- main.tf | 19 ++-- output.tf => outputs.tf | 2 +- tests/testing.tftest.hcl | 92 ++++++++++++++++--- variables.tf | 48 ++++++++-- 9 files changed, 143 insertions(+), 48 deletions(-) rename output.tf => outputs.tf (93%) diff --git a/.config/frontend_ip_configuration_parameters.json b/.config/frontend_ip_configuration_parameters.json index 4b761fb..c9a16d5 100644 --- a/.config/frontend_ip_configuration_parameters.json +++ b/.config/frontend_ip_configuration_parameters.json @@ -8,22 +8,19 @@ "Support": [] }, { - "Name": "private_ip_address", - "Description": "The Private IP Address to use for the Application Gateway.", + "Name": "subnet_id", + "Description": "The ID of the Subnet in which the Application Gateway should be deployed.", "Type": "string", "Default": "null", - "Required": "no", + "Required": "yes", "Support": [] }, { - "Name": "private_ip_address_allocation", - "Description": "The Allocation Method for the Private IP Address. Possible values are", + "Name": "private_ip_address", + "Description": "The Private IP Address to use for the Application Gateway.", "Type": "string", "Default": "null", "Required": "no", - "Support": [ - "Dynamic", - "Static" - ] + "Support": [] } ] \ No newline at end of file diff --git a/.config/http_listener_parameters.json b/.config/http_listener_parameters.json index 54612cb..e97b97f 100644 --- a/.config/http_listener_parameters.json +++ b/.config/http_listener_parameters.json @@ -39,7 +39,7 @@ }, { "Name": "host_name", - "Description": "The Hostname which should be used for this HTTP Listener. Setting this value changes Listener Type to Multi site", + "Description": "The Hostname which should be used for this HTTP Listener. Setting this value changes Listener Type to Multi site.", "Type": "string", "Default": "null", "Required": "no", diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7bd7e8f..c677e5a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.92.0 + rev: v1.92.1 hooks: - id: terraform_fmt name: Format Terraform code @@ -41,7 +41,7 @@ repos: fail_fast: true - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.92.0 + rev: v1.92.1 hooks: - id: terraform_validate name: Validate Terraform code diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d713bb..6e54b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ DEPRECATIONS: * **Parameter**: `waf_configuration` is deprecated in favor of `firewall_policy_id`. WAF configuration must now be performed using a firewall policy. [Retirement: Support for Application Gateway Web Application Firewall v2 Configuration is ending][waf-config-deprecate]. * **Parameter**: `sku` is deprecated in favor of `sku_name`. The `Standard_Small`, `Standard_Medium`, `Standard_Large` and `WAF_Medium` sku types are also deprecated. [Application Gateway V1 will be retired on 28 April 2026– Transition to Application Gateway V2][appgw-sku-deprecate]. +* **Parameter**: `private_ip_address_allocation` is deprecated. ## 1.2.0 (January 27, 2022) diff --git a/README.md b/README.md index 9c0043d..bbdfc8d 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,10 @@ The following parameters are supported: |enable\_http2|Enables HTTP/2 for the Application Gateway.|`bool`|`false`|no| |firewall\_policy\_id|The ID of the Firewall Policy to associate with the Application Gateway.|`string`|`null`|no| |capacity|The capacity (number of instances) of the Application Gateway. Possible values are between `1` and `125`.|`number`|`null`|no| -|autoscale\_configuration|A mapping with the autoscale configuration of the application gateway.|`object({})`|`null`|no| +|autoscale\_configuration|A mapping with the autoscale configuration of the Application Gateway.|`object({})`|`null`|no| |identity\_id|The ID of the Managed Identity to associate with the Application Gateway.|`string`|`null`|no| |subnet\_id|The ID of the Subnet which the Application Gateway should be connected to.|`string`|n/a|yes| -|frontend\_ip\_configuration|A mapping the front ip configuration.|`object({})`|n/a|yes| +|frontend\_ip\_configuration|A mapping with the frontend ip configuration of the Application Gateway.|`object({})`|n/a|yes| |backend\_address\_pools|List of objects that represent the configuration of each backend address pool.|`list(object({}))`|n/a|yes| |http\_listeners|List of objects that represent the configuration of each http listener.|`list(object({}))`|n/a|yes| |backend\_http\_settings|List of objects that represent the configuration of each backend http settings.|`list(object({}))`|n/a|yes| @@ -50,8 +50,8 @@ The `frontend_ip_configuration` supports the following: | Name | Description | Type | Default | Required | | ---- | ------------| :--: | :-----: | :------: | |public\_ip\_address\_id|The ID of a Public IP Address which the Application Gateway should use.|`string`|`null`|no| +|subnet\_id|The ID of the Subnet in which the Application Gateway should be deployed.|`string`|`null`|yes| |private\_ip\_address|The Private IP Address to use for the Application Gateway.|`string`|`null`|no| -|private\_ip\_address\_allocation|The Allocation Method for the Private IP Address. Possible values are `Dynamic` and `Static`.|`string`|`null`|no| The `backend_address_pools` supports the following: @@ -78,7 +78,7 @@ The `http_listeners` supports the following: |frontend\_ip\_configuration|The frontend ip configuration to use for this HTTP Listener. Possible values are `Public` and `Private`.|`string`|n/a|yes| |port|The port used for this HTTP Listener.|`number`|n/a|yes| |protocol|The Protocol to use for this HTTP Listener. Possible values are `Http` and `Https`.|`string`|n/a|yes| -|host\_name|The Hostname which should be used for this HTTP Listener. Setting this value changes Listener Type to Multi site|`string`|`null`|no| +|host\_name|The Hostname which should be used for this HTTP Listener. Setting this value changes Listener Type to Multi site.|`string`|`null`|no| |ssl\_certificate\_name|The name of the associated SSL Certificate which should be used for this HTTP Listener.|`string`|`null`|no| The `probes` supports the following: diff --git a/main.tf b/main.tf index af89d86..97c9883 100644 --- a/main.tf +++ b/main.tf @@ -47,7 +47,7 @@ resource "azurerm_application_gateway" "main" { content { name = "FrontendPrivateIpConfiguration" subnet_id = var.subnet_id - private_ip_address_allocation = var.frontend_ip_configuration.private_ip_address_allocation + private_ip_address_allocation = var.subnet_id != null ? "Static" : null private_ip_address = var.frontend_ip_configuration.private_ip_address } } @@ -62,14 +62,13 @@ resource "azurerm_application_gateway" "main" { } } - frontend_port { - name = "80" - port = 80 - } + dynamic "frontend_port" { + for_each = [for port in distinct(var.http_listeners[*].port) : port] - frontend_port { - name = "443" - port = 443 + content { + name = tostring(frontend_port.value) + port = frontend_port.value + } } # dynamic "ssl_certificate" { @@ -88,11 +87,11 @@ resource "azurerm_application_gateway" "main" { content { name = http_listener.value.name - frontend_ip_configuration_name = "FrontendPublicIpConfiguration" + frontend_ip_configuration_name = http_listener.value.frontend_ip_configuration == "Private" ? "FrontendPrivateIpConfiguration" : "FrontendPublicIpConfiguration" frontend_port_name = http_listener.value.port protocol = http_listener.value.protocol host_name = http_listener.value.host_name - ssl_certificate_name = http_listener.value.ssl_certificate_name + # ssl_certificate_name = http_listener.value.ssl_certificate_name } } diff --git a/output.tf b/outputs.tf similarity index 93% rename from output.tf rename to outputs.tf index 5ccb334..9637566 100644 --- a/output.tf +++ b/outputs.tf @@ -34,7 +34,7 @@ output "ssl_certificates" { } output "http_listeners" { - value = azurerm_application_gateway.main.http_listener + value = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener } description = "Blocks containing configuration of each http listener." } diff --git a/tests/testing.tftest.hcl b/tests/testing.tftest.hcl index 384e911..294250e 100644 --- a/tests/testing.tftest.hcl +++ b/tests/testing.tftest.hcl @@ -30,13 +30,17 @@ variables { frontend_ip_configuration = "Public" port = 80 protocol = "Http" - }, - # { - # name = "http-listener-2" - # frontend_ip_configuration = "Public" - # port = 80 - # protocol = "Http" - # } + }, { + name = "http-listener-2" + frontend_ip_configuration = "Private" + port = 8080 + protocol = "Http" + }, { + name = "http-listener-3" + frontend_ip_configuration = "Public" + port = 1433 + protocol = "Http" + } ] backend_http_settings = [{ name = "backend-http-setting", port = 80, protocol = "Http", request_timeout = 20 }] request_routing_rules = [{ @@ -68,10 +72,9 @@ run "plan" { identity_id = run.setup.managed_identity_id subnet_id = run.setup.subnet_id frontend_ip_configuration = { - subnet_id = run.setup.subnet_id - public_ip_address_id = run.setup.public_ip_id - private_ip_address_allocation = "Static" - private_ip_address = cidrhost(run.setup.subnet_address_prefix, 10) + public_ip_address_id = run.setup.public_ip_id + subnet_id = run.setup.subnet_id + private_ip_address = cidrhost(run.setup.subnet_address_prefix, 10) } } @@ -190,6 +193,68 @@ run "plan" { condition = tolist(azurerm_application_gateway.main.identity[0].identity_ids) == tolist([run.setup.managed_identity_id]) error_message = "The Managed Identity ID is not as expected." } + + #region HTTP Listeners + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-1"].name == var.http_listeners[0].name + error_message = "The name of the first HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-1"].frontend_ip_configuration_name == "FrontendPublicIpConfiguration" + error_message = "The frontend_ip_configuration of the first HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-1"].frontend_port_name == tostring(var.http_listeners[0].port) + error_message = "The port of the first HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-1"].protocol == var.http_listeners[0].protocol + error_message = "The protocol of the first HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-2"].name == var.http_listeners[1].name + error_message = "The name of the second HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-2"].frontend_ip_configuration_name == "FrontendPrivateIpConfiguration" + error_message = "The frontend_ip_configuration of the second HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-2"].frontend_port_name == tostring(var.http_listeners[1].port) + error_message = "The port of the second HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-2"].protocol == var.http_listeners[1].protocol + error_message = "The protocol of the second HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-3"].name == var.http_listeners[2].name + error_message = "The name of the third HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-3"].frontend_ip_configuration_name == "FrontendPublicIpConfiguration" + error_message = "The frontend_ip_configuration of the third HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-3"].frontend_port_name == tostring(var.http_listeners[2].port) + error_message = "The port of the third HTTP Listener is not as expected." + } + + assert { + condition = { for listener in azurerm_application_gateway.main.http_listener : listener.name => listener }["http-listener-3"].protocol == var.http_listeners[2].protocol + error_message = "The protocol of the third HTTP Listener is not as expected." + } } run "apply" { @@ -200,14 +265,13 @@ run "apply" { resource_group_name = run.setup.resource_group_name location = run.setup.resource_group_location tags = run.setup.resource_group_tags - firewall_policy_id = run.setup.firewall_policy_id + # firewall_policy_id = run.setup.firewall_policy_id identity_id = run.setup.managed_identity_id subnet_id = run.setup.subnet_id frontend_ip_configuration = { subnet_id = run.setup.subnet_id public_ip_address_id = run.setup.public_ip_id - private_ip_address_allocation = "Static" - private_ip_address = cidrhost(run.setup.subnet_address_prefix, 10) + # private_ip_address = cidrhost(run.setup.subnet_address_prefix, 10) } } diff --git a/variables.tf b/variables.tf index 32a431d..0787863 100644 --- a/variables.tf +++ b/variables.tf @@ -84,7 +84,7 @@ variable "autoscale_configuration" { max_capacity = number }) default = null - description = "A mapping with the autoscale configuration of the application gateway." + description = "A mapping with the autoscale configuration of the Application Gateway." validation { condition = var.autoscale_configuration != null ? var.autoscale_configuration.min_capacity >= 0 && var.autoscale_configuration.min_capacity <= 100 : true @@ -110,12 +110,26 @@ variable "subnet_id" { variable "frontend_ip_configuration" { type = object({ - subnet_id = optional(string) - public_ip_address_id = string - private_ip_address_allocation = optional(string) - private_ip_address = optional(string) + subnet_id = optional(string) + public_ip_address_id = string + private_ip_address = optional(string) }) - description = "A mapping the front ip configuration." + description = "A mapping with the frontend ip configuration of the Application Gateway." + + validation { + condition = var.frontend_ip_configuration.subnet_id != null ? var.frontend_ip_configuration.private_ip_address != null : true + error_message = "The private_ip_address is required when the subnet_id is provided." + } + + validation { + condition = var.frontend_ip_configuration.private_ip_address != null ? var.frontend_ip_configuration.subnet_id != null : true + error_message = "The subnet_id is required when the private_ip_address is provided." + } + + validation { + condition = var.frontend_ip_configuration.subnet_id != null ? can(cidrnetmask("${var.frontend_ip_configuration.private_ip_address}/32")) : true + error_message = "The private_ip_address must be formatted according to the CIDR standard without a mask." + } } variable "backend_address_pools" { @@ -148,12 +162,32 @@ variable "http_listeners" { type = list(object({ name = string frontend_ip_configuration = string - port = string + port = number protocol = string host_name = optional(string) ssl_certificate_name = optional(string) })) description = "List of objects that represent the configuration of each http listener." + + validation { + condition = alltrue([for listener in var.http_listeners : contains(["Public", "Private"], listener.frontend_ip_configuration)]) + error_message = "The frontend_ip_configuration must be either Public or Private." + } + + validation { + condition = alltrue([for listener in var.http_listeners : var.frontend_ip_configuration.subnet_id != null if listener.frontend_ip_configuration == "Private"]) + error_message = "The frontend_ip_configuration.subnet_id must be provided when the frontend_ip_configuration is Private." + } + + validation { + condition = alltrue([for listener in var.http_listeners : contains(["Http", "Https"], listener.protocol)]) + error_message = "The protocol must be either Http or Https." + } + + # validation { + # condition = alltrue([for listener in var.http_listeners : listener.protocol == "Https" ? listener.ssl_certificate_name != null : true]) + # error_message = "The ssl_certificate_name is required when the protocol is Https." + # } } # variable "probes" { From dfbe817a503f4aad2c607e29f38249527e84afef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garrido=20S=C3=A1nchez?= Date: Sun, 4 Aug 2024 09:51:45 +0000 Subject: [PATCH 2/2] fix unit tests --- tests/testing.tftest.hcl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/testing.tftest.hcl b/tests/testing.tftest.hcl index 294250e..85a99f4 100644 --- a/tests/testing.tftest.hcl +++ b/tests/testing.tftest.hcl @@ -265,13 +265,13 @@ run "apply" { resource_group_name = run.setup.resource_group_name location = run.setup.resource_group_location tags = run.setup.resource_group_tags - # firewall_policy_id = run.setup.firewall_policy_id + firewall_policy_id = run.setup.firewall_policy_id identity_id = run.setup.managed_identity_id subnet_id = run.setup.subnet_id frontend_ip_configuration = { - subnet_id = run.setup.subnet_id - public_ip_address_id = run.setup.public_ip_id - # private_ip_address = cidrhost(run.setup.subnet_address_prefix, 10) + subnet_id = run.setup.subnet_id + public_ip_address_id = run.setup.public_ip_id + private_ip_address = cidrhost(run.setup.subnet_address_prefix, 10) } }