Skip to content

Commit e8a5fe0

Browse files
authored
Feat: Azure Container Apps infrastructure (#3646)
2 parents 979f743 + 0a2dd1a commit e8a5fe0

20 files changed

Lines changed: 668 additions & 39 deletions

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "cal-itp/benefits",
44
"dockerComposeFile": ["../compose.yml"],
55
"service": "dev",
6-
"runServices": ["dev", "docs", "server", "postgres", "pgweb"],
6+
"runServices": ["dev", "docs", "server", "postgres", "pgadmin"],
77
"forwardPorts": ["docs:8001", "server:8000"],
88
"otherPortsAttributes": { "onAutoForward": "ignore" },
99
"workspaceFolder": "/calitp/app",

.env.sample

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ POSTGRES_PASSWORD=postgres
2727
POSTGRES_PORT=5432
2828
POSTGRES_SSLMODE=disable
2929

30-
PGWEB_PORT=8081
30+
PGADMIN_PORT=8081
3131

3232
# Email sending
3333
AZURE_COMMUNICATION_CONNECTION_STRING=

compose.yml

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,30 @@ services:
6767
volumes:
6868
- pgdata:/var/lib/postgresql/data
6969
healthcheck:
70-
test: pg_isready -U pgweb -h 127.0.0.1
70+
test: pg_isready -U ${POSTGRES_USER:-postgres} -h 127.0.0.1
7171
interval: 5s
7272

73-
pgweb:
74-
container_name: pgweb
75-
image: sosedoff/pgweb
73+
pgadmin:
74+
image: dpage/pgadmin4:latest
7675
ports:
77-
- "${PGWEB_PORT:-8081}:8081"
76+
- "${PGADMIN_PORT:-8081}:80"
7877
depends_on:
7978
postgres:
8079
condition: service_healthy
81-
healthcheck:
82-
test: ["CMD", "nc", "-vz", "127.0.0.1", "8081"]
83-
interval: 5s
8480
environment:
85-
- PGWEB_DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${DJANGO_DB_NAME}?sslmode=disable
81+
- PGADMIN_DEFAULT_EMAIL=benefits-admin@calitp.org
82+
- PGADMIN_DEFAULT_PASSWORD=admin
83+
- PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=false
84+
- PGADMIN_CONFIG_SERVER_MODE=false
85+
- PGHOST=${POSTGRES_HOSTNAME}
86+
- PGUSER=${POSTGRES_USER}
87+
- PGSSLMODE=${POSTGRES_SSLMODE}
88+
- PGPASSWORD=${POSTGRES_PASSWORD}
89+
volumes:
90+
- pgadmin_data:/var/lib/pgadmin
8691

8792
volumes:
8893
pgdata:
8994
driver: local
95+
pgadmin_data:
96+
driver: local

docs/reference/environment-variables.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -351,17 +351,13 @@ Comma-separated list of URIs. Configures the [`style-src`][mdn-csp-style-src] Co
351351

352352
The official PostgreSQL Docker image has more on available [`postgres` environment variables][postgres]
353353

354-
### `PGWEB_PORT`
355-
356-
The port to serve a (local only) [`pgweb`](https://sosedoff.github.io/pgweb/) service on.
357-
358354
### `POSTGRES_DB`
359355

360356
Used to define a different name for the default database that is created when the image is first started. If it is not specified, then the value of [`POSTGRES_USER`](#postgres_user) will be used.
361357

362358
### `POSTGRES_HOSTNAME`
363359

364-
The hostname of the (local) PostgreSQL Docker service, which is usually just the name of the service itself (`postgres`). Used by e.g. `pgweb` to establish a connection. Or the hostname of a cloud-based PostgreSQL service for e.g. Django to establish a connection.
360+
The hostname of the (local) PostgreSQL Docker service, which is usually just the name of the service itself (`postgres`). Or the hostname of a cloud-based PostgreSQL service for e.g. Django to establish a connection.
365361

366362
### `POSTGRES_PASSWORD`
367363

terraform/.terraform.lock.hcl

Lines changed: 14 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

terraform/database.tf

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
locals {
22
postgres_admin_password_secret_name = "postgres-admin-password"
3+
postgres_admin_login = "postgres_admin"
4+
postgres_admin_db = "postgres"
5+
pgadmin_admin_password_secret_name = "pgadmin-admin-password"
6+
pgadmin_config_db = "pgadmin_config"
7+
pgadmin_config_db_uri_secret_name = "pgadmin-config-db-uri"
38
}
49

510
# Manage an Azure Database for PostgreSQL Flexible Server
@@ -22,15 +27,15 @@ resource "azurerm_postgresql_flexible_server" "main" {
2227
public_network_access_enabled = false
2328
private_dns_zone_id = azurerm_private_dns_zone.db.id # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/postgresql_flexible_server#private_dns_zone_id-1
2429
delegated_subnet_id = azurerm_subnet.main["DB"].id
25-
administrator_login = "postgres_admin"
30+
administrator_login = local.postgres_admin_login
2631
administrator_password = azurerm_key_vault_secret.postgres_admin_password.value
2732

2833
lifecycle {
2934
ignore_changes = [tags]
3035
}
3136
}
3237

33-
# Generate a random password for PostgreSQL
38+
# Generate a random password for the PostgreSQL Admin
3439
resource "random_password" "postgres_admin_password" {
3540
length = 32
3641
min_lower = 4
@@ -51,3 +56,44 @@ resource "azurerm_key_vault_secret" "postgres_admin_password" {
5156
random_password.postgres_admin_password # Ensure password is generated first
5257
]
5358
}
59+
60+
# Generate a random password for the pgAdmin Admin
61+
resource "random_password" "pgadmin_admin_password" {
62+
length = 32
63+
min_lower = 4
64+
min_upper = 4
65+
min_numeric = 4
66+
min_special = 4
67+
special = true
68+
override_special = "_%@!-"
69+
}
70+
71+
# Create the secret for pgAdmin Admin Password using the generated password
72+
resource "azurerm_key_vault_secret" "pgadmin_admin_password" {
73+
name = local.pgadmin_admin_password_secret_name
74+
value = random_password.pgadmin_admin_password.result
75+
key_vault_id = azurerm_key_vault.main.id
76+
content_type = "password"
77+
depends_on = [
78+
random_password.pgadmin_admin_password # Ensure password is generated first
79+
]
80+
}
81+
82+
# Create a dedicated database for pgAdmin's configuration
83+
resource "azurerm_postgresql_flexible_server_database" "pgadmin_config" {
84+
name = local.pgadmin_config_db
85+
server_id = azurerm_postgresql_flexible_server.main.id
86+
charset = "UTF8"
87+
collation = "en_US.utf8"
88+
}
89+
90+
# Create a connection string URI for pgAdmin's configuration DB
91+
resource "azurerm_key_vault_secret" "pgadmin_config_db_uri" {
92+
name = local.pgadmin_config_db_uri_secret_name
93+
value = "'postgresql://${local.postgres_admin_login}:${urlencode(random_password.postgres_admin_password.result)}@${azurerm_postgresql_flexible_server.main.fqdn}:5432/${azurerm_postgresql_flexible_server_database.pgadmin_config.name}'"
94+
key_vault_id = azurerm_key_vault.main.id
95+
content_type = "password"
96+
depends_on = [
97+
random_password.postgres_admin_password
98+
]
99+
}

terraform/key_vault.tf

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,13 @@ locals {
4242
"Backup",
4343
"Restore",
4444
]
45+
46+
key_vault_name = "KV-CDT-PUB-CALITP-${local.env_letter}-001"
47+
key_vault_secret_uri_prefix = "https://${local.key_vault_name}.vault.azure.net/secrets"
4548
}
4649

4750
resource "azurerm_key_vault" "main" {
48-
name = "KV-CDT-PUB-CALITP-${local.env_letter}-001"
51+
name = local.key_vault_name
4952
location = data.azurerm_resource_group.main.location
5053
resource_group_name = data.azurerm_resource_group.main.name
5154
sku_name = "standard"
@@ -100,3 +103,27 @@ resource "azurerm_key_vault_access_policy" "webapp" {
100103
# This ensures the Key Vault itself is created before trying to attach a policy.
101104
depends_on = [azurerm_key_vault.main]
102105
}
106+
107+
# Standalone Access Policy for the Benefits (web) Container App's Managed Identity
108+
resource "azurerm_key_vault_access_policy" "web_container_app" {
109+
key_vault_id = azurerm_key_vault.main.id
110+
tenant_id = data.azurerm_client_config.current.tenant_id
111+
object_id = module.application.web_principal_id
112+
113+
secret_permissions = ["Get"]
114+
115+
# This ensures the Key Vault itself is created before trying to attach a policy.
116+
depends_on = [azurerm_key_vault.main]
117+
}
118+
119+
# Standalone Access Policy for the pgAdmin Container App's Managed Identity
120+
resource "azurerm_key_vault_access_policy" "pgadmin_container_app" {
121+
key_vault_id = azurerm_key_vault.main.id
122+
tenant_id = data.azurerm_client_config.current.tenant_id
123+
object_id = module.application.pgadmin_principal_id
124+
125+
secret_permissions = ["Get"]
126+
127+
# This ensures the Key Vault itself is created before trying to attach a policy.
128+
depends_on = [azurerm_key_vault.main]
129+
}

terraform/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ terraform {
44
required_providers {
55
azurerm = {
66
source = "hashicorp/azurerm"
7-
version = "~> 4.0"
7+
version = "~> 4.67"
88
}
99
}
1010

terraform/modules.tf

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
module "application" {
2+
source = "./modules/application"
3+
4+
# Terraform Environment
5+
resource_group_name = data.azurerm_resource_group.main.name
6+
location = data.azurerm_resource_group.main.location
7+
env_letter = local.env_letter
8+
env_name = local.env_name
9+
is_dev = local.is_dev
10+
is_prod = local.is_prod
11+
12+
# Monitoring
13+
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
14+
15+
# Network
16+
subnet_ca_id = azurerm_subnet.main["CA"].id
17+
18+
# Storage
19+
storage_account_name = azurerm_storage_account.main.name
20+
storage_account_access_key = azurerm_storage_account.main.primary_access_key
21+
storage_share_web_name = azurerm_storage_share.web.name
22+
storage_share_pgadmin_name = azurerm_storage_share.pgadmin.name
23+
24+
# Benefits Container App Image Details
25+
container_registry = var.CONTAINER_REGISTRY
26+
container_repository = var.CONTAINER_REPOSITORY
27+
container_tag = var.CONTAINER_TAG
28+
29+
# Key Vault
30+
key_vault_secret_uri_prefix = local.key_vault_secret_uri_prefix
31+
32+
# App Config
33+
azure_communication_connection_string_name = local.azure_communication_connection_string_name
34+
django_db_password_secret_name = azurerm_key_vault_secret.django_db_password.name
35+
postgres_admin_password_secret_name = azurerm_key_vault_secret.postgres_admin_password.name
36+
sender_email = local.sender_email
37+
postgres_fqdn = azurerm_postgresql_flexible_server.main.fqdn
38+
postgres_admin_login = local.postgres_admin_login
39+
postgres_admin_db = local.postgres_admin_db
40+
41+
# pgAdmin Config
42+
pgadmin_admin_password_secret_name = azurerm_key_vault_secret.pgadmin_admin_password.name
43+
pgadmin_config_db_uri_secret_name = azurerm_key_vault_secret.pgadmin_config_db_uri.name
44+
}

0 commit comments

Comments
 (0)