diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aacfc58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.terraform +.terraform.lock.hcl +terraform.tfvars +!examples/**/terraform.tfvars + +# Compiled files +*.tfstate +*.tfstate.backup +*.tfvars + +**/.terraform.lock.hcl diff --git a/README.md b/README.md index 52779d4..4a5b2f5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# terraform-aws-template +# Azure Service Bus feature [![Lint Status](https://github.com/tothenew/terraform-aws-template/workflows/Lint/badge.svg)](https://github.com/tothenew/terraform-aws-template/actions) [![LICENSE](https://img.shields.io/github/license/tothenew/terraform-aws-template)](https://github.com/tothenew/terraform-aws-template/blob/master/LICENSE) @@ -21,23 +21,142 @@ The following content needed to be created and managed: ## Providers -No providers. +| Name | Version | +|------|---------| +| azurecaf | ~> 1.2, >= 1.2.22 | +| azurerm | ~> 3.39 | ## Modules -No modules. +This Terraform module creates an [Azure Service Bus](https://docs.microsoft.com/en-us/azure/service-bus/). + +```hcl + + +module "servicebus" { + source = "claranet/service-bus/azurerm" + version = "x.x.x" + + location = module.azure_region.location + location_short = module.azure_region.location_short + client_name = var.client_name + environment = var.environment + stack = var.stack + + resource_group_name = module.rg.resource_group_name + + namespace_parameters = { + sku = "Premium" + } + + namespace_authorizations = { + listen = true + send = false + } + + # Network rules + network_rules_enabled = false + trusted_services_allowed = true + + + servicebus_queues = [{ + name = "myqueue" + default_message_ttl = "P1D" # 1 day + + dead_lettering_on_message_expiration = true + + authorizations = { + listen = true + send = false + } + }] + + servicebus_topics = [{ + name = "mytopic" + default_message_ttl = 5 # 5min + + authorizations = { + listen = true + send = true + manage = false + } + }] + + extra_tags = { + foo = "bar" + } +} +``` ## Resources -No resources. +| Name | Type | +|------|------| +| [azurerm_servicebus_namespace.servicebus_namespace](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_namespace) | resource | +| [azurerm_servicebus_namespace_authorization_rule.listen](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_namespace_authorization_rule) | resource | +| [azurerm_servicebus_namespace_authorization_rule.manage](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_namespace_authorization_rule) | resource | +| [azurerm_servicebus_namespace_authorization_rule.send](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_namespace_authorization_rule) | resource | +| [azurerm_servicebus_namespace_network_rule_set.network_rules](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_namespace_network_rule_set) | resource | +| [azurerm_servicebus_queue.queue](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_queue) | resource | +| [azurerm_servicebus_queue_authorization_rule.listen](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_queue_authorization_rule) | resource | +| [azurerm_servicebus_queue_authorization_rule.manage](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_queue_authorization_rule) | resource | +| [azurerm_servicebus_queue_authorization_rule.send](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_queue_authorization_rule) | resource | +| [azurerm_servicebus_topic.topic](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_topic) | resource | +| [azurerm_servicebus_topic_authorization_rule.listen](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_topic_authorization_rule) | resource | +| [azurerm_servicebus_topic_authorization_rule.manage](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_topic_authorization_rule) | resource | +| [azurerm_servicebus_topic_authorization_rule.send](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_topic_authorization_rule) | resource | ## Inputs -No inputs. +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| allowed\_cidrs | List of CIDR to allow access to that Service Bus Namespace. | `list(string)` | `[]` | no | +| client\_name | Client name/account used in naming | `string` | n/a | yes | +| default\_firewall\_action | Which default firewalling policy to apply. Valid values are `Allow` or `Deny`. | `string` | `"Deny"` | no | +| default\_tags\_enabled | Option to enable or disable default tags | `bool` | `true` | no | +| environment | Project environment | `string` | n/a | yes | +| extra\_tags | Extra tags to add | `map(string)` | `{}` | no | +| identity\_ids | Specifies a list of User Assigned Managed Identity IDs to be assigned to this Service Bus. | `list(string)` | `null` | no | +| identity\_type | Specifies the type of Managed Service Identity that should be configured on this Service Bus. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both). | `string` | `"SystemAssigned"` | no | +| location | Azure location for Servicebus. | `string` | n/a | yes | +| name\_prefix | Optional prefix for the generated name | `string` | `""` | no | +| name\_suffix | Optional suffix for the generated name | `string` | `""` | no | +| namespace\_authorizations | Object to specify which Namespace Authorization Rules need to be created. |
object({
listen = optional(bool, true)
send = optional(bool, true)
manage = optional(bool, true)
})
| `{}` | no | +| namespace\_parameters | Object to handle Service Bus Namespace options.
custom_name         = To override default resource name, generated if not set.
sku = Defines which tier to use. Options are `Basic`, `Standard` or `Premium`.
capacity = Specifies the capacity. When SKU is `Premium`, capacity can be 1, 2, 4, 8 or 16.
local_auth_enabled = Whether or not SAS authentication is enabled for the Service Bus Namespace.
zone_redundant = Whether or not this resource is zone redundant. SKU needs to be `Premium`.
minimum_tls_version = The minimum supported TLS version for this Service Bus Namespace.

public_network_access_enabled = Is public network access enabled for the Service Bus Namespace?
|
object({
custom_name = optional(string)
sku = optional(string, "Standard")
capacity = optional(number, 0)
local_auth_enabled = optional(bool, true)
zone_redundant = optional(bool, false)
minimum_tls_version = optional(string, "1.2")

public_network_access_enabled = optional(bool, true)
})
| `{}` | no | +| network\_rules\_enabled | Boolean to enable Network Rules on the Service Bus Namespace, requires `trusted_services_allowed`, `allowed_cidrs`, `subnet_ids` or `default_firewall_action` correctly set if enabled. | `bool` | `false` | no | +| resource\_group\_name | Name of the resource group | `string` | n/a | yes | +| servicebus\_queues | List of objects to create Queues with their options.
name        = Short Queue name.
custom_name = Custom name for Azure resource.

status = The status of the Queue. Possible values are `Active`, `Creating`, `Deleting`, `Disabled`, `ReceiveDisabled`, `Renaming`, `SendDisabled`, `Unknown`. Note that `Restoring` is not accepted.

auto_delete_on_idle = Duration of the idle interval after which the Queue is automatically deleted.
default_message_ttl = Duration of the TTL of messages sent to this Queue.
duplicate_detection_history_time_window = Duration during which duplicates can be detected.
lock_duration = Duration of a peek-lock that is, the amount of time that the message is locked for other receivers. Maximum value is 5 minutes.
max_message_size_in_kilobytes = Integer value which controls the maximum size of a message allowed on the Queue for Premium SKU.
max_size_in_megabytes = Integer value which controls the size of memory allocated for the Queue.
max_delivery_count = Integer value which controls when a message is automatically dead lettered.

enable_batched_operations = Boolean flag which controls whether server-side batched operations are enabled.
enable_partitioning = Boolean flag which controls whether to enable the Queue to be partitioned across multiple message brokers. Partitioning is available at entity creation for all Queues and Topics in Basic or Standard SKUs.
enable_express = Boolean flag which controls whether Express Entities are enabled. An express Queue holds a message in memory temporarily before writing it to persistent storage.
dead_lettering_on_message_expiration = Boolean flag which controls whether the Queue has dead letter support when a message expires.
requires_duplicate_detection = Boolean flag which controls whether the Queue requires duplicate detection.
requires_session = Boolean flag which controls whether the Queue requires sessions. This will allow ordered handling of unbounded sequences of related messages. With sessions enabled a Queue can guarantee first-in-first-out delivery of messages.

forward_to = The name of a Queue or Topic to automatically forward messages to.
forward_dead_lettered_messages_to = The name of a Queue or Topic to automatically forward dead lettered messages to.

authorizations_custom_name = To override default Queue Authorization Rules names, generated if not set (first with the custom name of the Queue if set, otherwise with Azure CAF).
authorizations = Object with `listen`, `send` and `manage` attributes to create Queues Authorizations Rules.
|
list(object({
name = string
custom_name = optional(string)

status = optional(string, "Active")

auto_delete_on_idle = optional(string)
default_message_ttl = optional(string)
duplicate_detection_history_time_window = optional(string)
lock_duration = optional(string)
max_message_size_in_kilobytes = optional(number)
max_size_in_megabytes = optional(number)
max_delivery_count = optional(number, 10)

enable_batched_operations = optional(bool, true)
enable_partitioning = optional(bool)
enable_express = optional(bool)
dead_lettering_on_message_expiration = optional(bool)
requires_duplicate_detection = optional(bool)
requires_session = optional(bool)

forward_to = optional(string)
forward_dead_lettered_messages_to = optional(string)

authorizations_custom_name = optional(string)
authorizations = optional(object({
listen = optional(bool, true)
send = optional(bool, true)
manage = optional(bool, true)
}), {})
}))
| `[]` | no | +| servicebus\_topics | List of objects to create Topics with their options.
name        = Short Topic name.
custom_name = Custom name for Azure resource.

status = The status of the Service Bus Topic. Acceptable values are `Active` or `Disabled`.

auto_delete_on_idle = Duration of the idle interval after which the Topic is automatically deleted, minimum of 5 minutes.
default_message_ttl = Duration of TTL of messages sent to this Topic if no TTL value is set on the message itself.
duplicate_detection_history_time_window = Duration during which duplicates can be detected.
max_message_size_in_kilobytes = Integer value which controls the maximum size of a message allowed on the Topic for `Premium` SKU.
max_size_in_megabytes = Integer value which controls the size of memory allocated for the Topic.

enable_batched_operations = Boolean flag which controls if server-side batched operations are enabled.
enable_partitioning = Boolean flag which controls whether to enable the Topic to be partitioned across multiple message brokers.
enable_express = Boolean flag which controls whether Express Entities are enabled. An express Topic holds a message in memory temporarily before writing it to persistent storage.
requires_duplicate_detection = Boolean flag which controls whether the Topic requires duplicate detection.
support_ordering = Boolean flag which controls whether the Topic supports ordering.

authorizations_custom_name = To override default Topic Authorization Rules names, generated if not set (first with the custom name of the Topic if set, otherwise with Azure CAF).
authorizations = Object with `listen`, `send` and `manage` attributes to create Topics Authorizations Rules.

subscriptions = List of subscriptions per Topic.
|
list(object({
name = string
custom_name = optional(string)

status = optional(string, "Active")

auto_delete_on_idle = optional(string)
default_message_ttl = optional(string)
duplicate_detection_history_time_window = optional(string)
max_message_size_in_kilobytes = optional(number)
max_size_in_megabytes = optional(number)

enable_batched_operations = optional(bool)
enable_partitioning = optional(bool)
enable_express = optional(bool)
requires_duplicate_detection = optional(bool)
support_ordering = optional(bool)

authorizations_custom_name = optional(string)
authorizations = optional(object({
listen = optional(bool, true)
send = optional(bool, true)
manage = optional(bool, true)
}), {})

# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_subscription
subscriptions = optional(list(object({
name = string
custom_name = optional(string)

status = optional(string, "Active")

auto_delete_on_idle = optional(string)
default_message_ttl = optional(string)
lock_duration = optional(string)
max_delivery_count = number

enable_batched_operations = optional(bool, true)
dead_lettering_on_message_expiration = optional(bool)
dead_lettering_on_filter_evaluation_error = optional(bool)
requires_session = optional(bool)

forward_to = optional(string)
forward_dead_lettered_messages_to = optional(string)
})), [])
}))
| `[]` | no | +| stack | Project stack name | `string` | n/a | yes | +| subnet\_ids | Subnets to allow access to that Service Bus Namespace. | `list(string)` | `[]` | no | +| trusted\_services\_allowed | If True, then Azure Services that are known and trusted for this resource type are allowed to bypass firewall configuration. | `bool` | `true` | no | +| use\_caf\_naming | Use the Azure CAF naming provider to generate default resource name. `custom_name` override this if set. Legacy default name is used if this is set to `false`. | `bool` | `true` | no | ## Outputs -No outputs. +| Name | Description | +|------|-------------| +| namespace | Service Bus Namespace outputs. | +| namespace\_listen\_authorization\_rule | Service Bus namespace listen only authorization rule. | +| namespace\_manage\_authorization\_rule | Service Bus namespace manage authorization rule. | +| namespace\_send\_authorization\_rule | Service Bus namespace send only authorization rule. | +| queues | Service Bus queues outputs. | +| queues\_listen\_authorization\_rule | Service Bus queues listen only authorization rules. | +| queues\_manage\_authorization\_rule | Service Bus queues manage authorization rules. | +| queues\_send\_authorization\_rule | Service Bus queues send only authorization rules. | +| topics | Service Bus topics outputs. | +| topics\_listen\_authorization\_rule | Service Bus topics listen only authorization rules. | +| topics\_manage\_authorization\_rule | Service Bus topics manage authorization rules. | +| topics\_send\_authorization\_rule | Service Bus topics send only authorization rules. | ## Authors diff --git a/_locals.tf b/_locals.tf new file mode 100644 index 0000000..6a87f93 --- /dev/null +++ b/_locals.tf @@ -0,0 +1,37 @@ +locals { + + name_prefix = lower(var.name_prefix) + name_suffix = lower(var.name_suffix) + + default_tags = var.default_tags_enabled ? { + env = var.environment + stack = var.stack + } : {} + + queues = try({ for q in var.servicebus_queues : q.name => q }, {}) + topics = try({ for t in var.servicebus_topics : t.name => t }, {}) + + + queues_auth = flatten([ + for q_name, q in local.queues : [ + for rule in ["listen", "send", "manage"] : { + queue = q_name + rule = rule + custom_name = q.custom_name + authorizations_custom_name = q.authorizations_custom_name + authorizations = q.authorizations + } + ] + ]) + topics_auth = flatten([ + for t_name, t in local.topics : [ + for rule in ["listen", "send", "manage"] : { + topic = t_name + rule = rule + custom_name = t.custom_name + authorizations_custom_name = t.authorizations_custom_name + authorizations = t.authorizations + } + ] + ]) +} \ No newline at end of file diff --git a/_namings.tf b/_namings.tf new file mode 100644 index 0000000..e639d32 --- /dev/null +++ b/_namings.tf @@ -0,0 +1,70 @@ +data "azurecaf_name" "servicebus_namespace" { + name = var.stack + resource_type = "azurerm_servicebus_namespace" + prefixes = var.name_prefix == "" ? null : [local.name_prefix] + suffixes = compact([var.client_name, var.environment, local.name_suffix, var.use_caf_naming ? "" : "bus"]) + use_slug = var.use_caf_naming + clean_input = true + separator = "-" +} + +data "azurecaf_name" "servicebus_queue" { + for_each = local.queues + + name = var.stack + resource_type = "azurerm_servicebus_queue" + prefixes = var.name_prefix == "" ? null : [local.name_prefix] + suffixes = compact([var.client_name, var.environment, each.key, local.name_suffix]) + use_slug = var.use_caf_naming + clean_input = true + separator = "-" +} + +data "azurecaf_name" "servicebus_namespace_auth_rule" { + for_each = toset(["listen", "send", "manage"]) + + name = var.stack + resource_type = "azurerm_servicebus_namespace_authorization_rule" + prefixes = var.name_prefix == "" ? null : [local.name_prefix] + suffixes = compact([var.client_name, var.environment, each.key, local.name_suffix]) + use_slug = var.use_caf_naming + clean_input = true + separator = "-" +} + +data "azurecaf_name" "servicebus_topic" { + for_each = local.topics + + name = var.stack + resource_type = "azurerm_servicebus_topic" + prefixes = var.name_prefix == "" ? null : [local.name_prefix] + suffixes = compact([var.client_name, var.environment, each.key, local.name_suffix]) + use_slug = var.use_caf_naming + clean_input = true + separator = "-" +} + + +data "azurecaf_name" "servicebus_queue_auth_rule" { + for_each = { for a in local.queues_auth : format("%s.%s", a.queue, a.rule) => format("%s-%s", a.queue, a.rule) } + + name = var.stack + resource_type = "azurerm_servicebus_queue_authorization_rule" + prefixes = var.name_prefix == "" ? null : [local.name_prefix] + suffixes = compact([var.client_name, var.environment, each.value, local.name_suffix]) + use_slug = var.use_caf_naming + clean_input = true + separator = "-" +} + +data "azurecaf_name" "servicebus_topic_auth_rule" { + for_each = { for a in local.topics_auth : format("%s.%s", a.topic, a.rule) => format("%s-%s", a.topic, a.rule) } + + name = var.stack + resource_type = "azurerm_servicebus_topic_authorization_rule" + prefixes = var.name_prefix == "" ? null : [local.name_prefix] + suffixes = compact([var.client_name, var.environment, each.value, local.name_suffix]) + use_slug = var.use_caf_naming + clean_input = true + separator = "-" +} \ No newline at end of file diff --git a/_outputs.tf b/_outputs.tf new file mode 100644 index 0000000..c15d628 --- /dev/null +++ b/_outputs.tf @@ -0,0 +1,82 @@ +output "namespace" { + description = "Service Bus Namespace outputs." + value = azurerm_servicebus_namespace.servicebus_namespace + sensitive = true +} + +output "namespace_listen_authorization_rule" { + description = "Service Bus namespace listen only authorization rule." + value = try(azurerm_servicebus_namespace_authorization_rule.listen["enabled"], null) +} + +output "namespace_send_authorization_rule" { + description = "Service Bus namespace send only authorization rule." + value = try(azurerm_servicebus_namespace_authorization_rule.send["enabled"], null) +} + +output "namespace_manage_authorization_rule" { + description = "Service Bus namespace manage authorization rule." + value = try(azurerm_servicebus_namespace_authorization_rule.manage["enabled"], null) +} + +output "queues" { + description = "Service Bus queues outputs." + value = { for q_name in keys(local.queues) : q_name => azurerm_servicebus_queue.queue[q_name] } +} + +output "topics" { + description = "Service Bus topics outputs." + value = { for t_name in keys(local.topics) : t_name => azurerm_servicebus_topic.topic[t_name] } +} + +output "queues_listen_authorization_rule" { + description = "Service Bus queues listen only authorization rules." + value = { + for a in local.queues_auth : + a.queue => azurerm_servicebus_queue_authorization_rule.listen[format("%s.%s", a.queue, a.rule)] if a.rule == "listen" && a.authorizations.listen + } + sensitive = true +} + +output "queues_send_authorization_rule" { + description = "Service Bus queues send only authorization rules." + value = { + for a in local.queues_auth : + a.queue => azurerm_servicebus_queue_authorization_rule.send[format("%s.%s", a.queue, a.rule)] if a.rule == "send" && a.authorizations.send + } +} + +output "queues_manage_authorization_rule" { + description = "Service Bus queues manage authorization rules." + value = { + for a in local.queues_auth : + a.queue => azurerm_servicebus_queue_authorization_rule.manage[format("%s.%s", a.queue, a.rule)] if a.rule == "manage" && a.authorizations.manage + } + sensitive = true +} + +output "topics_listen_authorization_rule" { + description = "Service Bus topics listen only authorization rules." + value = { + for a in local.topics_auth : + a.topic => azurerm_servicebus_topic_authorization_rule.listen[format("%s.%s", a.topic, a.rule)] if a.rule == "listen" && a.authorizations.listen + } + sensitive = true +} + +output "topics_send_authorization_rule" { + description = "Service Bus topics send only authorization rules." + value = { + for a in local.topics_auth : + a.topic => azurerm_servicebus_topic_authorization_rule.send[format("%s.%s", a.topic, a.rule)] if a.rule == "send" && a.authorizations.send + } + sensitive = true +} + +output "topics_manage_authorization_rule" { + description = "Service Bus topics manage authorization rules." + value = { + for a in local.topics_auth : + a.topic => azurerm_servicebus_topic_authorization_rule.manage[format("%s.%s", a.topic, a.rule)] if a.rule == "manage" && a.authorizations.manage + } +} \ No newline at end of file diff --git a/_variables.tf b/_variables.tf new file mode 100644 index 0000000..c619ff0 --- /dev/null +++ b/_variables.tf @@ -0,0 +1,190 @@ +# Generic naming variables +variable "name_prefix" { + description = "Optional prefix for the generated name" + type = string + default = "" +} + +variable "name_suffix" { + description = "Optional suffix for the generated name" + type = string + default = "" +} + +variable "use_caf_naming" { + description = "Use the Azure CAF naming provider to generate default resource name. `custom_name` override this if set. Legacy default name is used if this is set to `false`." + type = bool + default = true +} + +# Storage Firewall + +variable "network_rules_enabled" { + description = "Boolean to enable Network Rules on the Service Bus Namespace, requires `trusted_services_allowed`, `allowed_cidrs`, `subnet_ids` or `default_firewall_action` correctly set if enabled." + type = bool + default = false +} + +variable "trusted_services_allowed" { + description = "If True, then Azure Services that are known and trusted for this resource type are allowed to bypass firewall configuration." + type = bool + default = true +} + +variable "allowed_cidrs" { + description = "List of CIDR to allow access to that Service Bus Namespace." + type = list(string) + default = [] +} + +variable "subnet_ids" { + description = "Subnets to allow access to that Service Bus Namespace." + type = list(string) + default = [] +} + +variable "default_firewall_action" { + description = "Which default firewalling policy to apply. Valid values are `Allow` or `Deny`." + type = string + default = "Deny" +} + +variable "default_tags_enabled" { + description = "Option to enable or disable default tags" + type = bool + default = true +} + +variable "extra_tags" { + description = "Extra tags to add" + type = map(string) + default = {} +} + + +variable "client_name" { + description = "Client name/account used in naming" + type = string +} + +variable "environment" { + description = "Project environment" + type = string +} + +variable "stack" { + description = "Project stack name" + type = string +} + +variable "resource_group_name" { + description = "Name of the resource group" + type = string +} + +variable "location" { + description = "Azure location for Servicebus." + type = string +} + + +# Identity +variable "identity_type" { + description = "Specifies the type of Managed Service Identity that should be configured on this Service Bus. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both)." + type = string + default = "SystemAssigned" +} + +variable "identity_ids" { + description = "Specifies a list of User Assigned Managed Identity IDs to be assigned to this Service Bus." + type = list(string) + default = null +} + +variable "namespace_parameters" { + type = object({ + custom_name = optional(string) + sku = optional(string, "Standard") + capacity = optional(number, 0) + local_auth_enabled = optional(bool, true) + zone_redundant = optional(bool, false) + minimum_tls_version = optional(string, "1.2") + + public_network_access_enabled = optional(bool, true) + }) + default = {} +} + +variable "namespace_authorizations" { + description = "Object to specify which Namespace Authorization Rules need to be created." + type = object({ + listen = optional(bool, true) + send = optional(bool, true) + manage = optional(bool, true) + }) + default = {} +} + +variable "servicebus_queues" { + type = list(object({ + name = string + custom_name = optional(string) + + status = optional(string, "Active") + + auto_delete_on_idle = optional(string) + default_message_ttl = optional(string) + duplicate_detection_history_time_window = optional(string) + lock_duration = optional(string) + max_message_size_in_kilobytes = optional(number) + max_size_in_megabytes = optional(number) + max_delivery_count = optional(number, 10) + + enable_batched_operations = optional(bool, true) + enable_partitioning = optional(bool) + enable_express = optional(bool) + dead_lettering_on_message_expiration = optional(bool) + requires_duplicate_detection = optional(bool) + requires_session = optional(bool) + + forward_to = optional(string) + forward_dead_lettered_messages_to = optional(string) + + authorizations_custom_name = optional(string) + authorizations = optional(object({ + listen = optional(bool, true) + send = optional(bool, true) + manage = optional(bool, true) + }), {}) + })) + default = [] +} + +variable "servicebus_topics" { + type = list(object({ + name = string + custom_name = optional(string) + + status = optional(string, "Active") + + auto_delete_on_idle = optional(string) + default_message_ttl = optional(string) + duplicate_detection_history_time_window = optional(string) + max_message_size_in_kilobytes = optional(number) + max_size_in_megabytes = optional(number) + + enable_batched_operations = optional(bool) + enable_partitioning = optional(bool) + enable_express = optional(bool) + requires_duplicate_detection = optional(bool) + support_ordering = optional(bool) + + authorizations_custom_name = optional(string) + authorizations = optional(object({ + listen = optional(bool, true) + send = optional(bool, true) + manage = optional(bool, true) + }), {}) + })) + default = [] +} diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..dd141bf --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,11 @@ +.terraform +.terraform.lock.hcl +terraform.tfvars + +# Compiled files +*.tfstate +*.tfstate.backup +*.tfvars + +**/.terraform.lock.hcl + diff --git a/example/main.tf b/example/main.tf new file mode 100644 index 0000000..ccd5a71 --- /dev/null +++ b/example/main.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.3" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.28" + } + } +} + +provider "azurerm" { + features {} +} diff --git a/example/modules.tf b/example/modules.tf new file mode 100644 index 0000000..ba14560 --- /dev/null +++ b/example/modules.tf @@ -0,0 +1,62 @@ +## Users can create the resource group here +resource "azurerm_resource_group" "main" { + name = "my-servicebus-rg1" + location = "eastus" +} + +module "servicebus" { + source = "git::https://github.com/tothenew/terraform-azure-servicebus.git?ref=servicebus" + + client_name = "test" + environment = "dev" + stack = "ci" + + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + + namespace_parameters = { + sku = "Standard" + } + + namespace_authorizations = { + listen = true + send = false + } + + # Network rules + network_rules_enabled = false + trusted_services_allowed = true + # allowed_cidrs = [ + # "1.2.3.4/32", + # ] + # subnet_ids = [ + # data.azurerm_subnet.example.id, + # ] + + servicebus_queues = [{ + name = "myqueue" + default_message_ttl = "P1D" # 1 day + + dead_lettering_on_message_expiration = true + + authorizations = { + listen = true + send = false + } + }] + + servicebus_topics = [{ + name = "mytopic" + default_message_ttl = 5 # 5min + + authorizations = { + listen = true + send = true + manage = false + } + }] + + extra_tags = { + createdBy = "Deepak" + } +} \ No newline at end of file diff --git a/servicebus-namespace.tf b/servicebus-namespace.tf new file mode 100644 index 0000000..ea64bb0 --- /dev/null +++ b/servicebus-namespace.tf @@ -0,0 +1,94 @@ +resource "azurerm_servicebus_namespace" "servicebus_namespace" { + name = coalesce(var.namespace_parameters.custom_name, data.azurecaf_name.servicebus_namespace.result) + location = var.location + resource_group_name = var.resource_group_name + + sku = var.namespace_parameters.sku + capacity = var.namespace_parameters.sku != "Premium" ? 0 : var.namespace_parameters.capacity + local_auth_enabled = var.namespace_parameters.local_auth_enabled + zone_redundant = var.namespace_parameters.sku != "Premium" ? false : var.namespace_parameters.zone_redundant + minimum_tls_version = var.namespace_parameters.minimum_tls_version + + public_network_access_enabled = var.namespace_parameters.public_network_access_enabled + + dynamic "identity" { + for_each = var.identity_type == null ? [] : ["enabled"] + content { + type = var.identity_type + identity_ids = var.identity_ids == "UserAssigned" ? var.identity_ids : null + } + } + + tags = merge( + local.default_tags, + var.extra_tags, + ) +} + + +resource "azurerm_servicebus_namespace_authorization_rule" "listen" { + for_each = toset(var.namespace_authorizations.listen ? ["enabled"] : []) + + name = var.use_caf_naming ? data.azurecaf_name.servicebus_namespace_auth_rule["listen"].result : "listen-default" + namespace_id = azurerm_servicebus_namespace.servicebus_namespace.id + + listen = true + send = false + manage = false +} + +# ---------------------------------- Azure Service Bus Authorization Rule Set ----------------------------------------------------------- +#-------------------------------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_servicebus_namespace_authorization_rule" "send" { + for_each = toset(var.namespace_authorizations.send ? ["enabled"] : []) + + name = var.use_caf_naming ? data.azurecaf_name.servicebus_namespace_auth_rule["send"].result : "send-default" + namespace_id = azurerm_servicebus_namespace.servicebus_namespace.id + + listen = false + send = true + manage = false +} + +resource "azurerm_servicebus_namespace_authorization_rule" "manage" { + for_each = toset(var.namespace_authorizations.manage ? ["enabled"] : []) + + name = var.use_caf_naming ? data.azurecaf_name.servicebus_namespace_auth_rule["manage"].result : "manage-default" + namespace_id = azurerm_servicebus_namespace.servicebus_namespace.id + + listen = true + send = true + manage = true +} + +# ---------------------------------- Azure Service Bus Network Rule Set ----------------------------------------------------------- +#-------------------------------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_servicebus_namespace_network_rule_set" "network_rules" { + count = var.network_rules_enabled ? 1 : 0 + + namespace_id = azurerm_servicebus_namespace.servicebus_namespace.id + + default_action = var.default_firewall_action + public_network_access_enabled = var.namespace_parameters.public_network_access_enabled + trusted_services_allowed = var.trusted_services_allowed + + dynamic "network_rules" { + for_each = var.subnet_ids != null ? var.subnet_ids : [] + iterator = subnet + content { + subnet_id = subnet.value + ignore_missing_vnet_service_endpoint = false + } + } + + ip_rules = var.allowed_cidrs + + lifecycle { + precondition { + condition = !var.network_rules_enabled || (var.network_rules_enabled && var.namespace_parameters.sku == "Premium") + error_message = "`var.namespace_parameters.sku` must be `Premium` to enable network rules." + } + } +} \ No newline at end of file diff --git a/servicebus-queues.tf b/servicebus-queues.tf new file mode 100644 index 0000000..9305f9b --- /dev/null +++ b/servicebus-queues.tf @@ -0,0 +1,68 @@ +resource "azurerm_servicebus_queue" "queue" { + for_each = local.queues + + name = coalesce(each.value.custom_name, data.azurecaf_name.servicebus_queue[each.key].result) + namespace_id = azurerm_servicebus_namespace.servicebus_namespace.id + + status = each.value.status + + lock_duration = try(format("PT%sM", tonumber(each.value.lock_duration)), each.value.lock_duration) + max_message_size_in_kilobytes = each.value.max_message_size_in_kilobytes + max_size_in_megabytes = each.value.max_size_in_megabytes + requires_duplicate_detection = each.value.requires_duplicate_detection + requires_session = each.value.requires_session + default_message_ttl = try(format("PT%sM", tonumber(each.value.default_message_ttl)), each.value.default_message_ttl) + + dead_lettering_on_message_expiration = each.value.dead_lettering_on_message_expiration + duplicate_detection_history_time_window = try(format("PT%sM", tonumber(each.value.duplicate_detection_history_time_window)), each.value.duplicate_detection_history_time_window) + + max_delivery_count = each.value.max_delivery_count + enable_batched_operations = each.value.enable_batched_operations + auto_delete_on_idle = try(format("PT%sM", tonumber(each.value.auto_delete_on_idle)), each.value.auto_delete_on_idle) + + enable_partitioning = var.namespace_parameters.sku != "Premium" ? each.value.enable_partitioning : false + enable_express = var.namespace_parameters.sku != "Premium" ? each.value.enable_express : false + + forward_to = each.value.forward_to + forward_dead_lettered_messages_to = each.value.forward_dead_lettered_messages_to +} + + +resource "azurerm_servicebus_queue_authorization_rule" "listen" { + for_each = { + for a in local.queues_auth : format("%s.listen", a.queue) => a if a.rule == "listen" && a.authorizations.listen + } + + name = try(format("%s-listen", coalesce(each.value.authorizations_custom_name, each.value.custom_name)), var.use_caf_naming ? data.azurecaf_name.servicebus_queue_auth_rule[each.key].result : "listen-default") + queue_id = azurerm_servicebus_queue.queue[each.value.queue].id + + listen = true + send = false + manage = false +} + +resource "azurerm_servicebus_queue_authorization_rule" "send" { + for_each = { + for a in local.queues_auth : format("%s.send", a.queue) => a if a.rule == "send" && a.authorizations.send + } + + name = try(format("%s-send", coalesce(each.value.authorizations_custom_name, each.value.custom_name)), var.use_caf_naming ? data.azurecaf_name.servicebus_queue_auth_rule[each.key].result : "send-default") + queue_id = azurerm_servicebus_queue.queue[each.value.queue].id + + listen = false + send = true + manage = false +} + +resource "azurerm_servicebus_queue_authorization_rule" "manage" { + for_each = { + for a in local.queues_auth : format("%s.manage", a.queue) => a if a.rule == "manage" && a.authorizations.manage + } + + name = try(format("%s-manage", coalesce(each.value.authorizations_custom_name, each.value.custom_name)), var.use_caf_naming ? data.azurecaf_name.servicebus_queue_auth_rule[each.key].result : "manage-default") + queue_id = azurerm_servicebus_queue.queue[each.value.queue].id + + listen = true + send = true + manage = true +} \ No newline at end of file diff --git a/servicebus-topics.tf b/servicebus-topics.tf new file mode 100644 index 0000000..ee92fb4 --- /dev/null +++ b/servicebus-topics.tf @@ -0,0 +1,62 @@ +resource "azurerm_servicebus_topic" "topic" { + for_each = local.topics + + name = coalesce(each.value.custom_name, data.azurecaf_name.servicebus_topic[each.key].result) + namespace_id = azurerm_servicebus_namespace.servicebus_namespace.id + + status = each.value.status + + auto_delete_on_idle = try(format("PT%sM", tonumber(each.value.auto_delete_on_idle)), each.value.auto_delete_on_idle) + default_message_ttl = try(format("PT%sM", tonumber(each.value.default_message_ttl)), each.value.default_message_ttl) + + duplicate_detection_history_time_window = try(format("PT%sM", tonumber(each.value.duplicate_detection_history_time_window)), each.value.duplicate_detection_history_time_window) + + enable_batched_operations = each.value.enable_batched_operations + enable_express = each.value.enable_express + enable_partitioning = var.namespace_parameters.sku != "Premium" ? each.value.enable_partitioning : null + + max_message_size_in_kilobytes = var.namespace_parameters.sku != "Premium" ? null : each.value.max_message_size_in_kilobytes + max_size_in_megabytes = each.value.max_size_in_megabytes + requires_duplicate_detection = each.value.requires_duplicate_detection + support_ordering = each.value.support_ordering +} + + +resource "azurerm_servicebus_topic_authorization_rule" "listen" { + for_each = { + for a in local.topics_auth : format("%s.listen", a.topic) => a if a.rule == "listen" && a.authorizations.listen + } + + name = try(format("%s-listen", coalesce(each.value.authorizations_custom_name, each.value.custom_name)), var.use_caf_naming ? data.azurecaf_name.servicebus_topic_auth_rule[each.key].result : "listen-default") + topic_id = azurerm_servicebus_topic.topic[each.value.topic].id + + listen = true + send = false + manage = false +} + +resource "azurerm_servicebus_topic_authorization_rule" "send" { + for_each = { + for a in local.topics_auth : format("%s.send", a.topic) => a if a.rule == "send" && a.authorizations.send + } + + name = try(format("%s-send", coalesce(each.value.authorizations_custom_name, each.value.custom_name)), var.use_caf_naming ? data.azurecaf_name.servicebus_topic_auth_rule[each.key].result : "send-default") + topic_id = azurerm_servicebus_topic.topic[each.value.topic].id + + listen = false + send = true + manage = false +} + +resource "azurerm_servicebus_topic_authorization_rule" "manage" { + for_each = { + for a in local.topics_auth : format("%s.manage", a.topic) => a if a.rule == "manage" && a.authorizations.manage + } + + name = try(format("%s-manage", coalesce(each.value.authorizations_custom_name, each.value.custom_name)), var.use_caf_naming ? data.azurecaf_name.servicebus_topic_auth_rule[each.key].result : "manage-default") + topic_id = azurerm_servicebus_topic.topic[each.value.topic].id + + listen = true + send = true + manage = true +} \ No newline at end of file diff --git a/versions.tf b/versions.tf index 12ad22a..e353723 100644 --- a/versions.tf +++ b/versions.tf @@ -1,3 +1,13 @@ terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.3" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.39" + } + azurecaf = { + source = "aztfmod/azurecaf" + version = "~> 1.2, >= 1.2.22" + } + } }