@swovohq/fuel 0.2.2-alpha.88 → 0.2.2-alpha.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +65 -2
  2. package/dist/commands/create-app.d.ts.map +1 -1
  3. package/dist/commands/create-app.js +9 -3
  4. package/dist/commands/create-app.js.map +1 -1
  5. package/dist/commands/infra-deploy.d.ts.map +1 -1
  6. package/dist/commands/infra-deploy.js +3 -0
  7. package/dist/commands/infra-deploy.js.map +1 -1
  8. package/dist/commands/infra-init.d.ts.map +1 -1
  9. package/dist/commands/infra-init.js +63 -6
  10. package/dist/commands/infra-init.js.map +1 -1
  11. package/dist/infra/credentials.d.ts.map +1 -1
  12. package/dist/infra/credentials.js +5 -2
  13. package/dist/infra/credentials.js.map +1 -1
  14. package/dist/infra/github-client.d.ts +6 -0
  15. package/dist/infra/github-client.d.ts.map +1 -1
  16. package/dist/infra/github-client.js +30 -0
  17. package/dist/infra/github-client.js.map +1 -1
  18. package/dist/infra/github-token-link.d.ts +4 -0
  19. package/dist/infra/github-token-link.d.ts.map +1 -0
  20. package/dist/infra/github-token-link.js +28 -0
  21. package/dist/infra/github-token-link.js.map +1 -0
  22. package/dist/infra/orchestrator.d.ts +4 -1
  23. package/dist/infra/orchestrator.d.ts.map +1 -1
  24. package/dist/infra/orchestrator.js +16 -7
  25. package/dist/infra/orchestrator.js.map +1 -1
  26. package/dist/infra/preflight.d.ts.map +1 -1
  27. package/dist/infra/preflight.js +9 -1
  28. package/dist/infra/preflight.js.map +1 -1
  29. package/dist/infra/template-engine.d.ts +0 -4
  30. package/dist/infra/template-engine.d.ts.map +1 -1
  31. package/dist/infra/template-engine.js +36 -12
  32. package/dist/infra/template-engine.js.map +1 -1
  33. package/dist/infra/types.d.ts.map +1 -1
  34. package/dist/infra/types.js +58 -6
  35. package/dist/infra/types.js.map +1 -1
  36. package/dist/infra-templates/TODO-azure-prod.md +119 -0
  37. package/dist/infra-templates/modules/azure-container-app/main.tf +124 -0
  38. package/dist/infra-templates/modules/azure-container-app/outputs.tf +24 -0
  39. package/dist/infra-templates/modules/azure-container-app/variables.tf +80 -0
  40. package/dist/infra-templates/modules/azure-container-apps-environment/main.tf +18 -0
  41. package/dist/infra-templates/modules/azure-container-apps-environment/outputs.tf +14 -0
  42. package/dist/infra-templates/modules/azure-container-apps-environment/variables.tf +24 -0
  43. package/dist/infra-templates/modules/azure-container-registry/main.tf +10 -0
  44. package/dist/infra-templates/modules/azure-container-registry/outputs.tf +14 -0
  45. package/dist/infra-templates/modules/azure-container-registry/variables.tf +20 -0
  46. package/dist/infra-templates/modules/azure-front-door/main.tf +80 -0
  47. package/dist/infra-templates/modules/azure-front-door/outputs.tf +20 -0
  48. package/dist/infra-templates/modules/azure-front-door/variables.tf +26 -0
  49. package/dist/infra-templates/modules/azure-github-oidc/main.tf +48 -0
  50. package/dist/infra-templates/modules/azure-github-oidc/outputs.tf +9 -0
  51. package/dist/infra-templates/modules/azure-github-oidc/variables.tf +69 -0
  52. package/dist/infra-templates/modules/azure-key-vault/main.tf +21 -0
  53. package/dist/infra-templates/modules/azure-key-vault/outputs.tf +11 -0
  54. package/dist/infra-templates/modules/azure-key-vault/variables.tf +43 -0
  55. package/dist/infra-templates/modules/azure-monitor/main.tf +29 -0
  56. package/dist/infra-templates/modules/azure-monitor/outputs.tf +17 -0
  57. package/dist/infra-templates/modules/azure-monitor/variables.tf +32 -0
  58. package/dist/infra-templates/modules/azure-network/main.tf +134 -0
  59. package/dist/infra-templates/modules/azure-network/outputs.tf +44 -0
  60. package/dist/infra-templates/modules/azure-network/variables.tf +56 -0
  61. package/dist/infra-templates/modules/azure-postgresql-flexible/main.tf +34 -0
  62. package/dist/infra-templates/modules/azure-postgresql-flexible/outputs.tf +20 -0
  63. package/dist/infra-templates/modules/azure-postgresql-flexible/variables.tf +54 -0
  64. package/dist/infra-templates/modules/azure-redis-cache/main.tf +32 -0
  65. package/dist/infra-templates/modules/azure-redis-cache/outputs.tf +15 -0
  66. package/dist/infra-templates/modules/azure-redis-cache/variables.tf +30 -0
  67. package/dist/infra-templates/modules/azure-resource-group/main.tf +5 -0
  68. package/dist/infra-templates/modules/azure-resource-group/outputs.tf +11 -0
  69. package/dist/infra-templates/modules/azure-resource-group/variables.tf +16 -0
  70. package/dist/infra-templates/modules/azure-sql-database/main.tf +63 -0
  71. package/dist/infra-templates/modules/azure-sql-database/outputs.tf +20 -0
  72. package/dist/infra-templates/modules/azure-sql-database/variables.tf +78 -0
  73. package/dist/infra-templates/modules/azure-storage-account/main.tf +13 -0
  74. package/dist/infra-templates/modules/azure-storage-account/outputs.tf +16 -0
  75. package/dist/infra-templates/modules/azure-storage-account/variables.tf +38 -0
  76. package/dist/infra-templates/modules/cloud-watch/event-bridge.tf +15 -0
  77. package/dist/infra-templates/modules/cloud-watch/lambda.tf +84 -0
  78. package/dist/infra-templates/modules/cloud-watch/lambda_function.zip +0 -0
  79. package/dist/infra-templates/modules/cloud-watch/lambda_function_deploy.zip +0 -0
  80. package/dist/infra-templates/modules/cloud-watch/main.tf +156 -0
  81. package/dist/infra-templates/modules/cloud-watch/sns.tf +3 -0
  82. package/dist/infra-templates/modules/cloud-watch/variables.tf +38 -0
  83. package/dist/infra-templates/modules/ecs-core/alb.tf +126 -0
  84. package/dist/infra-templates/modules/ecs-core/cloudwatch.tf +48 -0
  85. package/dist/infra-templates/modules/ecs-core/iam.tf +95 -0
  86. package/dist/infra-templates/modules/ecs-core/main.tf +12 -0
  87. package/dist/infra-templates/modules/ecs-core/outputs.tf +44 -0
  88. package/dist/infra-templates/modules/ecs-core/secrets.tf +9 -0
  89. package/dist/infra-templates/modules/ecs-core/security_groups.tf +73 -0
  90. package/dist/infra-templates/modules/ecs-core/variables.tf +96 -0
  91. package/dist/infra-templates/modules/ecs-service/ecs_autoscaling.tf +51 -0
  92. package/dist/infra-templates/modules/ecs-service/ecs_service.tf +39 -0
  93. package/dist/infra-templates/modules/ecs-service/ecs_task_definition.tf +56 -0
  94. package/dist/infra-templates/modules/ecs-service/variables.tf +170 -0
  95. package/dist/infra-templates/modules/elastic-container-registry/main.tf +32 -0
  96. package/dist/infra-templates/modules/elastic-container-registry/outputs.tf +9 -0
  97. package/dist/infra-templates/modules/elastic-container-registry/variables.tf +25 -0
  98. package/dist/infra-templates/modules/github-oidc-role/main.tf +46 -0
  99. package/dist/infra-templates/modules/github-oidc-role/outputs.tf +4 -0
  100. package/dist/infra-templates/modules/github-oidc-role/variables.tf +11 -0
  101. package/dist/infra-templates/modules/guardduty/main.tf +8 -0
  102. package/dist/infra-templates/modules/iam/main.tf +205 -0
  103. package/dist/infra-templates/modules/iam/outputs.tf +3 -0
  104. package/dist/infra-templates/modules/iam/variables.tf +4 -0
  105. package/dist/infra-templates/modules/random-key/main.tf +11 -0
  106. package/dist/infra-templates/modules/random-key/outputs.tf +4 -0
  107. package/dist/infra-templates/modules/random-key/variables.tf +10 -0
  108. package/dist/infra-templates/modules/redis/main.tf +47 -0
  109. package/dist/infra-templates/modules/redis/outputs.tf +8 -0
  110. package/dist/infra-templates/modules/redis/variables.tf +20 -0
  111. package/dist/infra-templates/modules/relational-database-service/main.tf +44 -0
  112. package/dist/infra-templates/modules/relational-database-service/outputs.tf +13 -0
  113. package/dist/infra-templates/modules/relational-database-service/security-group.tf +15 -0
  114. package/dist/infra-templates/modules/relational-database-service/variables.tf +50 -0
  115. package/dist/infra-templates/modules/secret/main.tf +14 -0
  116. package/dist/infra-templates/modules/secret/outputs.tf +3 -0
  117. package/dist/infra-templates/modules/secret/variables.tf +8 -0
  118. package/dist/infra-templates/modules/simple-storage-service/main.tf +28 -0
  119. package/dist/infra-templates/modules/simple-storage-service/variables.tf +4 -0
  120. package/dist/infra-templates/modules/vpc/data.tf +2 -0
  121. package/dist/infra-templates/modules/vpc/main.tf +76 -0
  122. package/dist/infra-templates/modules/vpc/outputs.tf +23 -0
  123. package/dist/infra-templates/modules/vpc/variables.tf +5 -0
  124. package/dist/infra-templates/package.json +6 -0
  125. package/dist/infra-templates/templates/azure/MANUAL_DEPLOY.md +168 -0
  126. package/dist/infra-templates/templates/azure/backend.tf +8 -0
  127. package/dist/infra-templates/templates/azure/data.tf +21 -0
  128. package/dist/infra-templates/templates/azure/default.tfvars +0 -0
  129. package/dist/infra-templates/templates/azure/locals.tf +116 -0
  130. package/dist/infra-templates/templates/azure/main.tf +421 -0
  131. package/dist/infra-templates/templates/azure/outputs.tf +40 -0
  132. package/dist/infra-templates/templates/azure/providers.tf +65 -0
  133. package/dist/infra-templates/templates/azure/variables.tf +69 -0
  134. package/dist/infra-templates/templates/azure/workflows/build_and_deploy.yml +155 -0
  135. package/dist/infra-templates/templates/azure/workflows/tests.yml +63 -0
  136. package/dist/infra-templates/templates/ecs/README.md +225 -0
  137. package/dist/infra-templates/templates/ecs/backend.tf +7 -0
  138. package/dist/infra-templates/templates/ecs/data.tf +6 -0
  139. package/dist/infra-templates/templates/ecs/default.tfvars +1 -0
  140. package/dist/infra-templates/templates/ecs/locals.tf +97 -0
  141. package/dist/infra-templates/templates/ecs/main.tf +185 -0
  142. package/dist/infra-templates/templates/ecs/outputs.tf +42 -0
  143. package/dist/infra-templates/templates/ecs/providers.tf +21 -0
  144. package/dist/infra-templates/templates/ecs/variables.tf +105 -0
  145. package/dist/infra-templates/templates/ecs/workflows/build_and_deploy.yml +188 -0
  146. package/dist/infra-templates/templates/ecs/workflows/tests.yml +65 -0
  147. package/package.json +2 -2
  148. package/schema/fuel.schema.json +78 -44
@@ -0,0 +1,48 @@
1
+ resource "azuread_application" "this" {
2
+ count = var.enabled && var.existing_client_id == null ? 1 : 0
3
+
4
+ display_name = "${var.project_name}-github-actions"
5
+ }
6
+
7
+ resource "azuread_service_principal" "this" {
8
+ count = var.enabled && var.existing_client_id == null ? 1 : 0
9
+
10
+ client_id = azuread_application.this[0].client_id
11
+ }
12
+
13
+ resource "azuread_application_federated_identity_credential" "this" {
14
+ for_each = var.enabled && var.existing_client_id == null ? toset(var.branches) : toset([])
15
+
16
+ application_id = azuread_application.this[0].id
17
+ display_name = "github-${each.value}"
18
+ audiences = ["api://AzureADTokenExchange"]
19
+ issuer = "https://token.actions.githubusercontent.com"
20
+ subject = "repo:${var.github_organization}/${var.github_repo_name}:ref:refs/heads/${each.value}"
21
+ }
22
+
23
+ # Looked up (not created) when the app/SP/federated credential were set up out-of-band by a
24
+ # tenant admin — see variables.tf for why. Skipped entirely when the caller already supplies
25
+ # existing_principal_id: the lookup needs Directory.Read.All/Application.Read.All on the
26
+ # deploying SP, which a lot of tenants won't grant just to resolve one known object ID.
27
+ data "azuread_service_principal" "existing" {
28
+ count = var.enabled && var.existing_client_id != null && var.existing_principal_id == null ? 1 : 0
29
+
30
+ client_id = var.existing_client_id
31
+ }
32
+
33
+ locals {
34
+ # null when disabled — the parent template must guard any reference to outputs on `var.enabled`.
35
+ principal_id = var.enabled ? (
36
+ var.existing_principal_id != null ? var.existing_principal_id : (
37
+ var.existing_client_id == null ? azuread_service_principal.this[0].object_id : data.azuread_service_principal.existing[0].object_id
38
+ )
39
+ ) : null
40
+ }
41
+
42
+ resource "azurerm_role_assignment" "contributor" {
43
+ count = var.enabled && var.manage_role_assignment ? 1 : 0
44
+
45
+ scope = var.role_assignment_scope
46
+ role_definition_name = "Contributor"
47
+ principal_id = local.principal_id
48
+ }
@@ -0,0 +1,9 @@
1
+ output "client_id" {
2
+ value = var.enabled ? (var.existing_client_id != null ? var.existing_client_id : azuread_application.this[0].client_id) : null
3
+ description = "Client ID of the Azure AD application used by GitHub Actions to authenticate via OIDC. Null when disabled."
4
+ }
5
+
6
+ output "principal_id" {
7
+ value = local.principal_id
8
+ description = "Object ID of the service principal, used by the parent template to grant additional roles (e.g. AcrPush/AcrPull). Null when disabled."
9
+ }
@@ -0,0 +1,69 @@
1
+ variable "project_name" {
2
+ description = "Project name used as prefix for resource names"
3
+ type = string
4
+ }
5
+
6
+ variable "github_organization" {
7
+ description = "GitHub organization name for the OIDC trust subject"
8
+ type = string
9
+ }
10
+
11
+ variable "github_repo_name" {
12
+ description = "GitHub repository name for the OIDC trust subject"
13
+ type = string
14
+ }
15
+
16
+ variable "branches" {
17
+ description = "Branches allowed to assume the Azure AD application via OIDC. One federated credential is created per branch."
18
+ type = list(string)
19
+ default = ["main"]
20
+ }
21
+
22
+ variable "role_assignment_scope" {
23
+ description = "Scope for the Contributor role assignment, typically the resource group ID"
24
+ type = string
25
+ }
26
+
27
+ variable "existing_client_id" {
28
+ description = <<-EOT
29
+ Client ID of an app registration + service principal + federated identity credential(s)
30
+ already created out-of-band by a tenant admin. Set this when the deploying SP lacks the
31
+ Entra ID "Application Administrator" role (creating an app registration requires it,
32
+ regardless of subscription-level RBAC) — the module then looks up and reuses that identity
33
+ instead of creating a new one. The Contributor role assignment is still managed by this
34
+ module either way, since that only needs "User Access Administrator" on the subscription.
35
+ EOT
36
+ type = string
37
+ default = null
38
+ }
39
+
40
+ variable "existing_principal_id" {
41
+ description = <<-EOT
42
+ Object ID (principal ID) of the service principal identified by existing_client_id, when
43
+ that's already known. Set this to skip looking it up via Microsoft Graph entirely — the
44
+ lookup (data.azuread_service_principal.existing) needs Directory.Read.All or
45
+ Application.Read.All on the deploying SP, which many tenant admins won't grant just to
46
+ resolve one known object. Whoever created the app out-of-band can get this by running
47
+ `az ad sp show --id <existing_client_id> --query id -o tsv`.
48
+ EOT
49
+ type = string
50
+ default = null
51
+ }
52
+
53
+ variable "manage_role_assignment" {
54
+ description = <<-EOT
55
+ Whether Terraform should create/manage the Contributor role assignment for this identity.
56
+ Set to false when it was already granted out-of-band alongside existing_client_id (e.g. a
57
+ tenant admin's onboarding runbook already ran `az role assignment create ... --role
58
+ Contributor`) — otherwise Terraform tries to create it again on every apply and fails with
59
+ RoleAssignmentExists.
60
+ EOT
61
+ type = bool
62
+ default = true
63
+ }
64
+
65
+ variable "enabled" {
66
+ description = "Set to false to skip this module entirely (no app, no SP, no role assignment). Use to unblock testing while waiting on a tenant admin for Application Administrator or an existing_client_id."
67
+ type = bool
68
+ default = true
69
+ }
@@ -0,0 +1,21 @@
1
+ # Application Key Vault (RBAC-based), used by apps to read secrets at runtime.
2
+ # Distinct from the bootstrap vault created by the CLI via SDK.
3
+ resource "azurerm_key_vault" "this" {
4
+ name = var.name
5
+ resource_group_name = var.resource_group_name
6
+ location = var.location
7
+ tenant_id = var.tenant_id
8
+ sku_name = var.sku_name
9
+
10
+ rbac_authorization_enabled = true
11
+ purge_protection_enabled = false
12
+ soft_delete_retention_days = 7
13
+
14
+ public_network_access_enabled = length(var.ip_rules) > 0 ? true : var.public_network_access_enabled
15
+
16
+ network_acls {
17
+ bypass = "AzureServices"
18
+ default_action = var.network_acls_default_action
19
+ ip_rules = var.ip_rules
20
+ }
21
+ }
@@ -0,0 +1,11 @@
1
+ output "id" {
2
+ value = azurerm_key_vault.this.id
3
+ }
4
+
5
+ output "vault_uri" {
6
+ value = azurerm_key_vault.this.vault_uri
7
+ }
8
+
9
+ output "name" {
10
+ value = azurerm_key_vault.this.name
11
+ }
@@ -0,0 +1,43 @@
1
+ variable "name" {
2
+ type = string
3
+ description = "Key Vault name"
4
+ }
5
+
6
+ variable "resource_group_name" {
7
+ type = string
8
+ description = "Resource group where the Key Vault is created"
9
+ }
10
+
11
+ variable "location" {
12
+ type = string
13
+ description = "Azure region"
14
+ }
15
+
16
+ variable "tenant_id" {
17
+ type = string
18
+ description = "Azure AD tenant ID"
19
+ }
20
+
21
+ variable "sku_name" {
22
+ type = string
23
+ description = "Key Vault SKU"
24
+ default = "standard"
25
+ }
26
+
27
+ variable "public_network_access_enabled" {
28
+ type = bool
29
+ description = "Allow access from the public internet. Defaults to false: this vault isn't read by anything at runtime today (no VNet/private-endpoint consumer wired up yet), so there's no reason to expose it publicly."
30
+ default = false
31
+ }
32
+
33
+ variable "network_acls_default_action" {
34
+ type = string
35
+ description = "Default action for requests not matched by an IP/VNet rule. Defaults to \"Deny\" — flip to \"Allow\" (or add a private endpoint) only once something outside the vault's own resource group needs to read from it."
36
+ default = "Deny"
37
+ }
38
+
39
+ variable "ip_rules" {
40
+ type = list(string)
41
+ description = "Public IPs/CIDRs allowed through the firewall (e.g. the operator machine running `tofu apply`, which needs data-plane access to write secrets). Forces public_network_access_enabled on when non-empty, since Azure ignores network_acls entirely while public access is off."
42
+ default = []
43
+ }
@@ -0,0 +1,29 @@
1
+ resource "azurerm_log_analytics_workspace" "this" {
2
+ name = "${var.project_name}-law"
3
+ resource_group_name = var.resource_group_name
4
+ location = var.location
5
+ sku = "PerGB2018"
6
+ retention_in_days = var.retention_days
7
+ }
8
+
9
+ resource "azurerm_application_insights" "this" {
10
+ name = "${var.project_name}-appi"
11
+ resource_group_name = var.resource_group_name
12
+ location = var.location
13
+ application_type = "web"
14
+ workspace_id = azurerm_log_analytics_workspace.this.id
15
+ daily_data_cap_in_gb = var.daily_data_cap_gb
16
+ retention_in_days = var.retention_days
17
+ }
18
+
19
+ resource "azurerm_portal_dashboard" "this" {
20
+ count = var.dashboard_count
21
+ name = "${var.project_name}-dashboard-${count.index + 1}"
22
+ resource_group_name = var.resource_group_name
23
+ location = var.location
24
+
25
+ dashboard_properties = jsonencode({
26
+ lenses = {}
27
+ metadata = {}
28
+ })
29
+ }
@@ -0,0 +1,17 @@
1
+ output "log_analytics_workspace_id" {
2
+ value = azurerm_log_analytics_workspace.this.id
3
+ }
4
+
5
+ output "instrumentation_key" {
6
+ value = azurerm_application_insights.this.instrumentation_key
7
+ sensitive = true
8
+ }
9
+
10
+ output "connection_string" {
11
+ value = azurerm_application_insights.this.connection_string
12
+ sensitive = true
13
+ }
14
+
15
+ output "app_insights_id" {
16
+ value = azurerm_application_insights.this.id
17
+ }
@@ -0,0 +1,32 @@
1
+ variable "project_name" {
2
+ type = string
3
+ description = "Project name, used to derive resource names"
4
+ }
5
+
6
+ variable "resource_group_name" {
7
+ type = string
8
+ description = "Resource group where the monitoring resources are created"
9
+ }
10
+
11
+ variable "location" {
12
+ type = string
13
+ description = "Azure region"
14
+ }
15
+
16
+ variable "daily_data_cap_gb" {
17
+ type = number
18
+ description = "Daily data ingestion cap in GB for Application Insights"
19
+ default = 1
20
+ }
21
+
22
+ variable "retention_days" {
23
+ type = number
24
+ description = "Data retention in days"
25
+ default = 90
26
+ }
27
+
28
+ variable "dashboard_count" {
29
+ type = number
30
+ description = "Number of portal dashboards to create"
31
+ default = 0
32
+ }
@@ -0,0 +1,134 @@
1
+ # Keeps every database/cache reachable only from inside this VNet. Container Apps
2
+ # ingress stays public (Front Door needs a public origin) — this only closes off
3
+ # the data-plane services that have no reason to be reachable from the internet.
4
+ resource "azurerm_virtual_network" "this" {
5
+ name = "${var.project_name}-vnet"
6
+ resource_group_name = var.resource_group_name
7
+ location = var.location
8
+ address_space = var.address_space
9
+ }
10
+
11
+ resource "azurerm_subnet" "container_apps_infra" {
12
+ name = "${var.project_name}-cae-subnet"
13
+ resource_group_name = var.resource_group_name
14
+ virtual_network_name = azurerm_virtual_network.this.name
15
+ address_prefixes = var.container_apps_subnet_prefix
16
+
17
+ delegation {
18
+ name = "container-apps-environment"
19
+ service_delegation {
20
+ name = "Microsoft.App/environments"
21
+ actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"]
22
+ }
23
+ }
24
+ }
25
+
26
+ # A subnet has no reliable outbound internet path of its own once placed in a custom
27
+ # VNet — Azure's "default outbound access" is being retired for newly created resources.
28
+ # Without this, every Container App revision hangs pulling its image (any image, from
29
+ # any registry) until the platform gives up ("Operation expired"). This is Microsoft's
30
+ # own recommended pattern for Container Apps in a BYO VNet.
31
+ resource "azurerm_public_ip" "container_apps_nat" {
32
+ name = "${var.project_name}-cae-nat-ip"
33
+ resource_group_name = var.resource_group_name
34
+ location = var.location
35
+ allocation_method = "Static"
36
+ sku = "Standard"
37
+ }
38
+
39
+ resource "azurerm_nat_gateway" "container_apps" {
40
+ name = "${var.project_name}-cae-nat"
41
+ resource_group_name = var.resource_group_name
42
+ location = var.location
43
+ sku_name = "Standard"
44
+ }
45
+
46
+ resource "azurerm_nat_gateway_public_ip_association" "container_apps" {
47
+ nat_gateway_id = azurerm_nat_gateway.container_apps.id
48
+ public_ip_address_id = azurerm_public_ip.container_apps_nat.id
49
+ }
50
+
51
+ resource "azurerm_subnet_nat_gateway_association" "container_apps" {
52
+ subnet_id = azurerm_subnet.container_apps_infra.id
53
+ nat_gateway_id = azurerm_nat_gateway.container_apps.id
54
+ }
55
+
56
+ # Postgres Flexible Server's private-access model is VNet integration (delegated_subnet_id
57
+ # + private_dns_zone_id directly on the server resource) rather than a standard Private
58
+ # Endpoint — it needs its own subnet delegated specifically to the Postgres RP.
59
+ resource "azurerm_subnet" "postgres" {
60
+ count = var.postgres_needed ? 1 : 0
61
+
62
+ name = "${var.project_name}-postgres-subnet"
63
+ resource_group_name = var.resource_group_name
64
+ virtual_network_name = azurerm_virtual_network.this.name
65
+ address_prefixes = var.postgres_subnet_prefix
66
+
67
+ delegation {
68
+ name = "postgres-flexible-server"
69
+ service_delegation {
70
+ name = "Microsoft.DBforPostgreSQL/flexibleServers"
71
+ actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"]
72
+ }
73
+ }
74
+ }
75
+
76
+ # Azure SQL and Azure Managed Redis use standard Private Link — a plain (non-delegated)
77
+ # subnet with network policies disabled, shared across both.
78
+ resource "azurerm_subnet" "private_endpoints" {
79
+ count = (var.mssql_needed || var.redis_needed) ? 1 : 0
80
+
81
+ name = "${var.project_name}-pe-subnet"
82
+ resource_group_name = var.resource_group_name
83
+ virtual_network_name = azurerm_virtual_network.this.name
84
+ address_prefixes = var.private_endpoints_subnet_prefix
85
+ private_endpoint_network_policies = "Disabled"
86
+ }
87
+
88
+ resource "azurerm_private_dns_zone" "postgres" {
89
+ count = var.postgres_needed ? 1 : 0
90
+
91
+ name = "privatelink.postgres.database.azure.com"
92
+ resource_group_name = var.resource_group_name
93
+ }
94
+
95
+ resource "azurerm_private_dns_zone_virtual_network_link" "postgres" {
96
+ count = var.postgres_needed ? 1 : 0
97
+
98
+ name = "${var.project_name}-postgres-link"
99
+ resource_group_name = var.resource_group_name
100
+ private_dns_zone_name = azurerm_private_dns_zone.postgres[0].name
101
+ virtual_network_id = azurerm_virtual_network.this.id
102
+ }
103
+
104
+ resource "azurerm_private_dns_zone" "mssql" {
105
+ count = var.mssql_needed ? 1 : 0
106
+
107
+ name = "privatelink.database.windows.net"
108
+ resource_group_name = var.resource_group_name
109
+ }
110
+
111
+ resource "azurerm_private_dns_zone_virtual_network_link" "mssql" {
112
+ count = var.mssql_needed ? 1 : 0
113
+
114
+ name = "${var.project_name}-mssql-link"
115
+ resource_group_name = var.resource_group_name
116
+ private_dns_zone_name = azurerm_private_dns_zone.mssql[0].name
117
+ virtual_network_id = azurerm_virtual_network.this.id
118
+ }
119
+
120
+ resource "azurerm_private_dns_zone" "redis" {
121
+ count = var.redis_needed ? 1 : 0
122
+
123
+ name = "privatelink.redis.azure.net"
124
+ resource_group_name = var.resource_group_name
125
+ }
126
+
127
+ resource "azurerm_private_dns_zone_virtual_network_link" "redis" {
128
+ count = var.redis_needed ? 1 : 0
129
+
130
+ name = "${var.project_name}-redis-link"
131
+ resource_group_name = var.resource_group_name
132
+ private_dns_zone_name = azurerm_private_dns_zone.redis[0].name
133
+ virtual_network_id = azurerm_virtual_network.this.id
134
+ }
@@ -0,0 +1,44 @@
1
+ output "vnet_id" {
2
+ value = azurerm_virtual_network.this.id
3
+ }
4
+
5
+ output "container_apps_subnet_id" {
6
+ value = azurerm_subnet.container_apps_infra.id
7
+ }
8
+
9
+ # Static outbound IP for the Container Apps environment — useful for allowlisting this
10
+ # project's apps against third-party APIs/firewalls that require a fixed source IP.
11
+ output "container_apps_outbound_ip" {
12
+ value = azurerm_public_ip.container_apps_nat.ip_address
13
+ }
14
+
15
+ output "postgres_subnet_id" {
16
+ value = try(azurerm_subnet.postgres[0].id, null)
17
+ }
18
+
19
+ output "private_endpoints_subnet_id" {
20
+ value = try(azurerm_subnet.private_endpoints[0].id, null)
21
+ }
22
+
23
+ output "postgres_dns_zone_id" {
24
+ value = try(azurerm_private_dns_zone.postgres[0].id, null)
25
+ }
26
+
27
+ output "mssql_dns_zone_id" {
28
+ value = try(azurerm_private_dns_zone.mssql[0].id, null)
29
+ }
30
+
31
+ output "redis_dns_zone_id" {
32
+ value = try(azurerm_private_dns_zone.redis[0].id, null)
33
+ }
34
+
35
+ # So callers can `depends_on = [module.network]` and be sure the VNet links (not just the
36
+ # zones) exist before creating a resource that needs private DNS resolution to work.
37
+ # Only includes links for zones actually created (postgres_needed/mssql_needed/redis_needed).
38
+ output "ready" {
39
+ value = compact([
40
+ try(azurerm_private_dns_zone_virtual_network_link.postgres[0].id, null),
41
+ try(azurerm_private_dns_zone_virtual_network_link.mssql[0].id, null),
42
+ try(azurerm_private_dns_zone_virtual_network_link.redis[0].id, null),
43
+ ])
44
+ }
@@ -0,0 +1,56 @@
1
+ variable "project_name" {
2
+ type = string
3
+ description = "Used as a naming prefix for the VNet, subnets, and private DNS zones"
4
+ }
5
+
6
+ variable "resource_group_name" {
7
+ type = string
8
+ description = "Resource group where networking resources are created"
9
+ }
10
+
11
+ variable "location" {
12
+ type = string
13
+ description = "Azure region"
14
+ }
15
+
16
+ variable "address_space" {
17
+ type = list(string)
18
+ description = "VNet address space"
19
+ default = ["10.20.0.0/16"]
20
+ }
21
+
22
+ variable "container_apps_subnet_prefix" {
23
+ type = list(string)
24
+ description = "Subnet for the Container Apps environment's infrastructure. /23 minimum (Consumption-only environments require /23; a /27 workload-profile environment would fit too)."
25
+ default = ["10.20.0.0/23"]
26
+ }
27
+
28
+ variable "postgres_subnet_prefix" {
29
+ type = list(string)
30
+ description = "Subnet delegated to Microsoft.DBforPostgreSQL/flexibleServers. Shared by every Postgres flexible server in this project (Azure minimum is /28; sized larger for headroom)."
31
+ default = ["10.20.2.0/26"]
32
+ }
33
+
34
+ variable "private_endpoints_subnet_prefix" {
35
+ type = list(string)
36
+ description = "Subnet for standard Private Link endpoints (Azure SQL, Azure Managed Redis)."
37
+ default = ["10.20.3.0/26"]
38
+ }
39
+
40
+ variable "postgres_needed" {
41
+ type = bool
42
+ description = "Whether any app in this project uses the Postgres engine. Skips the delegated subnet + private DNS zone when false."
43
+ default = true
44
+ }
45
+
46
+ variable "mssql_needed" {
47
+ type = bool
48
+ description = "Whether any app in this project uses the mssql engine. Skips its private DNS zone when false."
49
+ default = true
50
+ }
51
+
52
+ variable "redis_needed" {
53
+ type = bool
54
+ description = "Whether any app in this project has Redis enabled. Skips its private DNS zone when false."
55
+ default = true
56
+ }
@@ -0,0 +1,34 @@
1
+ resource "random_password" "this" {
2
+ length = 24
3
+ special = true
4
+ override_special = "!#$%&*()-_=+[]{}<>:?"
5
+ }
6
+
7
+ locals {
8
+ effective_password = var.admin_password != "" ? var.admin_password : random_password.this.result
9
+ }
10
+
11
+ resource "azurerm_postgresql_flexible_server" "this" {
12
+ name = var.server_name
13
+ resource_group_name = var.resource_group_name
14
+ location = var.location
15
+ administrator_login = var.admin_username
16
+ administrator_password = local.effective_password
17
+ sku_name = var.sku_name
18
+ storage_mb = var.storage_mb
19
+ version = "16"
20
+ # zone intentionally not specified — let Azure choose.
21
+
22
+ # VNet-integrated instead of public: reachable only from the delegated subnet's VNet
23
+ # (the Container Apps environment lives there too), no public endpoint at all.
24
+ delegated_subnet_id = var.delegated_subnet_id
25
+ private_dns_zone_id = var.private_dns_zone_id
26
+ public_network_access_enabled = false
27
+ }
28
+
29
+ resource "azurerm_postgresql_flexible_server_database" "this" {
30
+ server_id = azurerm_postgresql_flexible_server.this.id
31
+ name = var.database_name
32
+ collation = "en_US.utf8"
33
+ charset = "UTF8"
34
+ }
@@ -0,0 +1,20 @@
1
+ output "fqdn" {
2
+ value = azurerm_postgresql_flexible_server.this.fqdn
3
+ description = "Fully qualified domain name of the PostgreSQL Flexible Server"
4
+ }
5
+
6
+ output "database_name" {
7
+ value = azurerm_postgresql_flexible_server_database.this.name
8
+ description = "Name of the PostgreSQL database"
9
+ }
10
+
11
+ output "admin_username" {
12
+ value = var.admin_username
13
+ description = "Administrator username for the PostgreSQL Flexible Server"
14
+ }
15
+
16
+ output "admin_password" {
17
+ value = local.effective_password
18
+ sensitive = true
19
+ description = "Effective administrator password for the PostgreSQL Flexible Server"
20
+ }
@@ -0,0 +1,54 @@
1
+ variable "server_name" {
2
+ description = "Name of the PostgreSQL Flexible Server. Must be globally unique."
3
+ type = string
4
+ }
5
+
6
+ variable "database_name" {
7
+ description = "Name of the PostgreSQL database"
8
+ type = string
9
+ }
10
+
11
+ variable "resource_group_name" {
12
+ description = "Name of the resource group where the server will be created"
13
+ type = string
14
+ }
15
+
16
+ variable "location" {
17
+ description = "Azure region where the server will be created"
18
+ type = string
19
+ }
20
+
21
+ variable "admin_username" {
22
+ description = "Administrator login for the PostgreSQL Flexible Server"
23
+ type = string
24
+ default = "pgadmin"
25
+ }
26
+
27
+ variable "admin_password" {
28
+ description = "Administrator password for the PostgreSQL Flexible Server. Leave empty to auto-generate."
29
+ type = string
30
+ default = ""
31
+ sensitive = true
32
+ }
33
+
34
+ variable "sku_name" {
35
+ description = "SKU name for the PostgreSQL Flexible Server"
36
+ type = string
37
+ default = "GP_Standard_D4s_v3"
38
+ }
39
+
40
+ variable "storage_mb" {
41
+ description = "Max storage allowed for the PostgreSQL Flexible Server, in MB"
42
+ type = number
43
+ default = 32768
44
+ }
45
+
46
+ variable "delegated_subnet_id" {
47
+ description = "Subnet delegated to Microsoft.DBforPostgreSQL/flexibleServers. Postgres Flexible Server's private-access model wires this directly on the server, not via a separate private endpoint."
48
+ type = string
49
+ }
50
+
51
+ variable "private_dns_zone_id" {
52
+ description = "Private DNS zone (privatelink.postgres.database.azure.com) linked to the same VNet as delegated_subnet_id."
53
+ type = string
54
+ }
@@ -0,0 +1,32 @@
1
+ resource "azurerm_managed_redis" "this" {
2
+ name = var.name
3
+ resource_group_name = var.resource_group_name
4
+ location = var.location
5
+ sku_name = var.sku_name
6
+ public_network_access = "Disabled"
7
+
8
+ default_database {
9
+ client_protocol = "Encrypted"
10
+ clustering_policy = "EnterpriseCluster"
11
+ eviction_policy = "VolatileLRU"
12
+ }
13
+ }
14
+
15
+ resource "azurerm_private_endpoint" "this" {
16
+ name = "${var.name}-pe"
17
+ resource_group_name = var.resource_group_name
18
+ location = var.location
19
+ subnet_id = var.private_endpoint_subnet_id
20
+
21
+ private_service_connection {
22
+ name = "${var.name}-psc"
23
+ private_connection_resource_id = azurerm_managed_redis.this.id
24
+ subresource_names = ["redisEnterprise"]
25
+ is_manual_connection = false
26
+ }
27
+
28
+ private_dns_zone_group {
29
+ name = "default"
30
+ private_dns_zone_ids = [var.private_dns_zone_id]
31
+ }
32
+ }