Skip to content

Commit e6d0295

Browse files
mrlockstarrfk-nc
andauthored
feat: dtoss 9659 deploy azure service bus into cohort manager (#1164)
* DTOSS-9659: Create a service bus queue and link up with function app * Terraform formatting and template refs --------- Co-authored-by: Richard Kingston <[email protected]>
1 parent ca3716b commit e6d0295

File tree

10 files changed

+209
-11
lines changed

10 files changed

+209
-11
lines changed

.azuredevops/pipelines/cd-infrastructure-dev-core.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ resources:
1313
- repository: dtos-devops-templates
1414
type: github
1515
name: NHSDigital/dtos-devops-templates
16-
ref: 093557aee9d7ffb803106db24f51b7a5ef6f7f50
16+
ref: 2041f050215b170eef2853e78e010c2a949a9bae
1717
endpoint: NHSDigital
1818

1919
variables:

.azuredevops/pipelines/cd-infrastructure-int-core.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ resources:
1313
- repository: dtos-devops-templates
1414
type: github
1515
name: NHSDigital/dtos-devops-templates
16-
ref: 093557aee9d7ffb803106db24f51b7a5ef6f7f50
16+
ref: 2041f050215b170eef2853e78e010c2a949a9bae
1717
endpoint: NHSDigital
1818

1919
variables:

.azuredevops/pipelines/cd-infrastructure-nft-core.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ resources:
1313
- repository: dtos-devops-templates
1414
type: github
1515
name: NHSDigital/dtos-devops-templates
16-
ref: 093557aee9d7ffb803106db24f51b7a5ef6f7f50
16+
ref: 2041f050215b170eef2853e78e010c2a949a9bae
1717
endpoint: NHSDigital
1818

1919
variables:

infrastructure/tf-audit/locals.tf

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@ locals {
44
# ------------------------
55
# FinOps
66
# ------------------------
7-
TagVersion = "1" # Tag version (e.g. 1, 2, 3)
7+
TagVersion = "1" # Tag version (e.g. 1, 2, 3)
88
Programme-Service = "DToS/Breast Screening" # Programme name (custom value)
99
Product-Project = "Cohort Manager" # Product name (custom value)
10-
Owner = "<product owner>" # Business owner (name or email)
11-
CostCentre = "129106" # Billing code (e.g. 128943)
10+
Owner = "<product owner>" # Business owner (name or email)
11+
CostCentre = "129106" # Billing code (e.g. 128943)
1212

1313
# ------------------------
1414
# SecOps
1515
# ------------------------
16-
data_classification = "3" # Data level (1–5)
16+
data_classification = "3" # Data level (1–5)
1717
DataType = "PII" # Data type (None, PCD, PII, Anonymised, UserAccount, Audit)
1818
ProjectType = "In-development" # Project stage (PoC, Pilot, Production)
19-
PublicFacing = "Y" # Internet-facing (Y/N)
19+
PublicFacing = "Y" # Internet-facing (Y/N)
2020

2121
# ------------------------
2222
# TechOps

infrastructure/tf-core/environments/development.tfvars

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ function_apps = {
239239
name_suffix = "receive-caas-file"
240240
function_endpoint_name = "ReceiveCaasFile"
241241
app_service_plan_key = "DefaultPlan"
242+
producer_to_service_bus = ["dtoss-nsp"]
242243
db_connection_string = "DtOsDatabaseConnectionString"
243244
storage_account_env_var_name = "caasfolder_STORAGE"
244245
app_urls = [
@@ -290,6 +291,7 @@ function_apps = {
290291
DemographicURI = "https://dev-uks-durable-demographic-function.azurewebsites.net/api/DurableDemographicFunction_HttpStart/"
291292
GetOrchestrationStatusURL = "https://dev-uks-durable-demographic-function.azurewebsites.net/api/GetOrchestrationStatus"
292293
AllowDeleteRecords = true
294+
TopicName = "DistributeParticipantQueue"
293295
UpdateQueueName = "update-participant-queue"
294296
maxNumberOfChecks = "50"
295297
UseNewFunctions = "false"
@@ -1223,6 +1225,36 @@ key_vault = {
12231225
sku_name = "standard"
12241226
}
12251227

1228+
service_bus = {
1229+
distribute-participant = {
1230+
capacity = 1
1231+
sku_tier = "Premium"
1232+
max_payload_size = "100mb"
1233+
topics = {
1234+
cohort-distribution-queue = {
1235+
batched_operations_enabled = true
1236+
}
1237+
add-participant-queue = {
1238+
batched_operations_enabled = true
1239+
}
1240+
update-participant-queue = {
1241+
batched_operations_enabled = true
1242+
}
1243+
}
1244+
}
1245+
}
1246+
1247+
# service_bus_subscriptions = {
1248+
# subscriber_config = {
1249+
# event-dev-ap = {
1250+
# subscription_name = "events-sub"
1251+
# topic_name = "events"
1252+
# namespace_name = "dtoss-nsp"
1253+
# subscriber_functionName = "foundryRelay"
1254+
# }
1255+
# }
1256+
# }
1257+
12261258
sqlserver = {
12271259
sql_admin_group_name = "sqlsvr_cohman_dev_uks_admin"
12281260
ad_auth_only = true

infrastructure/tf-core/function_app.tf

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,52 @@ module "functionapp" {
6363
function_app_slots = var.function_app_slots
6464

6565
tags = var.tags
66+
67+
depends_on = [
68+
module.azure_service_bus
69+
]
70+
}
71+
72+
73+
locals {
74+
# Filter fa_config to only include those with producer_to_service_bus
75+
service_bus_function_app_map = {
76+
for app_key, app_value in var.function_apps.fa_config :
77+
app_key => app_value
78+
if contains(keys(app_value), "producer_to_service_bus")
79+
}
80+
81+
# There are multiple maps
82+
# We cannot nest for loops inside a map, so first iterate all permutations as a list of objects...
83+
unified_service_bus_object_list = flatten([
84+
for service_bus_key, service_bus_value in local.azure_service_bus_map : [
85+
for function_key, function_values in local.service_bus_function_app_map : merge({
86+
service_bus_key = service_bus_key # 1st iterator
87+
function_key = function_key # 2nd iterator
88+
service_bus_value = service_bus_value
89+
}, function_values) # the block of key/value pairs for a specific collection
90+
if contains(keys(function_values), "producer_to_service_bus")
91+
&& (function_values.producer_to_service_bus != null ? length(function_values.producer_to_service_bus) > 0 : false)
92+
]
93+
])
94+
# ...then project them into a map with unique keys (combining the iterators), for consumption by a for_each meta argument
95+
unified_service_bus_object_map = {
96+
for item in local.unified_service_bus_object_list :
97+
"${item.service_bus_key}-${item.function_key}" => item
98+
}
99+
}
100+
101+
# Use the merged map in your resources
102+
resource "azurerm_role_assignment" "function_send_to_topic" {
103+
for_each = local.unified_service_bus_object_map
104+
105+
principal_id = module.functionapp["${each.value.function_key}-${each.value.service_bus_value.region}"].function_app_sami_id
106+
role_definition_name = "Azure Service Bus Data Sender"
107+
scope = module.azure_service_bus[each.value.service_bus_key].namespace_id
108+
66109
}
67110

111+
68112
locals {
69113
app_settings_common = {
70114
DOCKER_ENABLE_CI = var.function_apps.docker_CI_enable
@@ -104,6 +148,10 @@ locals {
104148
)
105149
},
106150

151+
{
152+
for key, obj in local.unified_service_bus_object_map : "SERVICE_BUS_NAMESPACE" => "${module.azure_service_bus[obj.service_bus_key].namespace_name}.servicebus.windows.net"
153+
},
154+
107155
# Dynamic reference to Key Vault
108156
length(config.key_vault_url) > 0 ? {
109157
(config.key_vault_url) = module.key_vault[region].key_vault_url

infrastructure/tf-core/locals.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ locals {
1414
# ------------------------
1515
# SecOps
1616
# ------------------------
17-
data_classification = "3" # Data level (1–5)
17+
data_classification = "3" # Data level (1–5)
1818
DataType = "PII" # Data type (None, PCD, PII, Anonymised, UserAccount, Audit)
1919
ProjectType = "In-development" # Project stage (PoC, Pilot, Production)
2020
PublicFacing = "Y" # Internet-facing (Y/N)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# module "service_bus_subscriptions" {
2+
# for_each = local.service_bus_subscriptions_map
3+
4+
# source = "../../../dtos-devops-templates/infrastructure/modules/service-bus-subscription"
5+
6+
# subscription_name = each.value.service_bus_subscription_key
7+
# max_delivery_count = 10
8+
# topic_id = values(module.azure_service_bus["${each.value.namespace_name}-${each.value.region}"].topic_ids)[0]
9+
# namespace_name = "${each.value.namespace_name}-${each.value.region}"
10+
# service_bus_namespace_id = module.azure_service_bus["${each.value.namespace_name}-${each.value.region}"].namespace_id
11+
# function_app_principal_id = module.functionapp["${each.value.subscriber_functionName}-${each.value.region}"].function_app_sami_id
12+
13+
# }
14+
15+
# locals {
16+
17+
# service_bus_subscriptions_object_list = flatten([
18+
# for region in keys(var.regions) : [
19+
# for service_bus_subscription_key, service_bus_subscription_details in var.service_bus_subscriptions.subscriber_config : merge(
20+
# {
21+
# region = region # 1st iterator
22+
# service_bus_subscription_key = service_bus_subscription_key # 2nd iterator
23+
# },
24+
# service_bus_subscription_details # the rest of the key/value pairs for a specific service_bus
25+
# )
26+
# ]
27+
# ])
28+
29+
# # ...then project the list of objects into a map with unique keys (combining the iterators), for consumption by a for_each meta argument
30+
# service_bus_subscriptions_map = {
31+
# for object in local.service_bus_subscriptions_object_list : "${object.service_bus_subscription_key}-${object.region}" => object
32+
# }
33+
# }
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
module "azure_service_bus" {
2+
for_each = local.azure_service_bus_map
3+
4+
source = "../../../dtos-devops-templates/infrastructure/modules/service-bus"
5+
6+
servicebus_topic_map = each.value.topics
7+
# The namespace defaults to the object key unless a namespace is specified, then it overwrites it.
8+
servicebus_namespace_name = coalesce(each.value.namespace_name, each.key)
9+
resource_group_name = azurerm_resource_group.core[each.value.region].name
10+
location = each.value.region
11+
capacity = each.value.capacity
12+
sku_tier = each.value.sku_tier
13+
14+
# Private Endpoint Configuration if enabled
15+
private_endpoint_properties = var.features.private_endpoints_enabled ? {
16+
# This should be changed to service bus see https://github.com/NHSDigital/dtos-hub/blob/b9455d3a9c29f8837d75bcc3c67a111c2a49831d/infrastructure/dns_private.tf#L53
17+
# and https://github.com/NHSDigital/dtos-hub/blob/b9455d3a9c29f8837d75bcc3c67a111c2a49831d/infrastructure/dns_private.tf#L49
18+
private_dns_zone_ids = [data.terraform_remote_state.hub.outputs.private_dns_zones["${each.value.region}-event_hub"].id]
19+
private_endpoint_enabled = var.features.private_endpoints_enabled
20+
private_endpoint_subnet_id = module.subnets["${module.regions_config[each.value.region].names.subnet}-pep"].id
21+
private_endpoint_resource_group_name = azurerm_resource_group.rg_private_endpoints[each.value.region].name
22+
private_service_connection_is_manual = var.features.private_service_connection_is_manual
23+
} : null
24+
25+
tags = var.tags
26+
}
27+
28+
locals {
29+
30+
azure_service_bus_object_list = flatten([
31+
for region in keys(var.regions) : [
32+
for service_bus_key, service_bus_details in var.service_bus : merge(
33+
{
34+
region = region
35+
service_bus_key = service_bus_key
36+
},
37+
service_bus_details
38+
)
39+
]
40+
])
41+
42+
# ...then project the list of objects into a map with unique keys (combining the iterators), for consumption by a for_each meta argument
43+
azure_service_bus_map = {
44+
for object in local.azure_service_bus_object_list : "${object.service_bus_key}-${object.region}" => object
45+
}
46+
}
47+

infrastructure/tf-core/variables.tf

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,9 @@ variable "function_apps" {
237237
env_var_name = string
238238
container_name = string
239239
})), [])
240-
db_connection_string = optional(string, "")
241-
key_vault_url = optional(string, "")
240+
db_connection_string = optional(string, "")
241+
producer_to_service_bus = optional(list(string), [])
242+
key_vault_url = optional(string, "")
242243
app_urls = optional(list(object({
243244
env_var_name = string
244245
function_app_key = string
@@ -455,6 +456,43 @@ variable "sqlserver" {
455456
})
456457
}
457458

459+
variable "service_bus" {
460+
description = "Configuration for Service Bus namespaces and their topics"
461+
type = map(object({
462+
namespace_name = optional(string)
463+
capacity = number
464+
sku_tier = string
465+
max_payload_size = string
466+
topics = map(object({
467+
auto_delete_on_idle = optional(string, "P10675199DT2H48M5.4775807S")
468+
batched_operations_enabled = optional(bool, false)
469+
default_message_ttl = optional(string, "P10675199DT2H48M5.4775807S")
470+
duplicate_detection_history_time_window = optional(string)
471+
partitioning_enabled = optional(bool, false)
472+
max_message_size_in_kilobytes = optional(number, 1024)
473+
max_size_in_megabytes = optional(number, 5120)
474+
requires_duplicate_detection = optional(bool, false)
475+
support_ordering = optional(bool)
476+
status = optional(string, "Active")
477+
topic_name = optional(string)
478+
}))
479+
}))
480+
}
481+
482+
# variable "service_bus_subscriptions" {
483+
# description = "Configuration for service bus subscriptions"
484+
# type = object({
485+
# subscriber_config = map(object({
486+
# subscription_name = string
487+
# namespace_name = optional(string)
488+
# topic_name = string
489+
# subscriber_functionName = string
490+
# }))
491+
# })
492+
# default = {}
493+
# }
494+
495+
458496
variable "storage_accounts" {
459497
description = "Configuration for the Storage Account, currently used for Function Apps"
460498
type = map(object({

0 commit comments

Comments
 (0)