Skip to content

Commit b97d393

Browse files
Postgresql vacation planner (#94)
* Add web-app-postgresql-flexible-server vacation planner sample Sibling to web-app-cosmosdb-mongodb-api: same Vacation Planner Flask UI, backed by Azure Database for PostgreSQL flexible server fronted by a Private Endpoint (group `postgresqlServer`). The server runs in public-access mode with a permissive firewall rule so the deploy machine can run the post-create psql bootstrap; the Web App reaches the server through the Private Endpoint via the linked `privatelink.postgres.database.azure.com` Private DNS Zone. The deploy flow mirrors web-app-sql-database: after the IaC provisions the infra (server + database + private endpoint + Web App), psql creates a dedicated application role `testuser`, grants the minimum schema privileges on `PlannerDB`, creates the `activities` table, and seeds three demo rows. The Web App's `PG_USER` / `PG_PASSWORD` are set to `testuser` after psql — the server-admin login is never written to the Web App's app settings. Three IaC variants (scripts, Bicep, Terraform) deploy the same topology. All three derive `PG_HOST` and `PG_PORT` from the server's `fullyQualifiedDomainName` by splitting on `:`: LocalStack returns host:port (the dynamic TCP-proxy port that fronts the shared `postgres:18` container), real Azure returns just the bare host so `PG_PORT` defaults to 5432. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Vacation Planner Sample for Azure Database for PostgreSQL Flexible Server * Extend main README.md --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d202864 commit b97d393

110 files changed

Lines changed: 66220 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"permissions": {
3+
"additionalDirectories": [
4+
"/home/paolo/localstack/localstack-pro/localstack-pro-azure"
5+
],
6+
"allow": [
7+
"Read(/home/paolo/localstack/localstack-pro/localstack-pro-azure/**)",
8+
"Edit(/home/paolo/localstack/localstack-pro/localstack-pro-azure/**)",
9+
"Write(/home/paolo/localstack/localstack-pro/localstack-pro-azure/**)",
10+
"Bash(cd /home/paolo/localstack/localstack-pro/localstack-pro-azure:*)",
11+
"Bash(/home/paolo/localstack/localstack-pro/localstack-pro-azure/**)"
12+
]
13+
}
14+
}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ This repository contains comprehensive sample projects demonstrating how to deve
3434
| [Web App and CosmosDB for NoSQL API ](./samples/web-app-cosmosdb-nosql-api/python/README.md) | Azure Web App using CosmosDB for NoSQL API |
3535
| [Web App and Managed Identities](./samples/web-app-managed-identity/python/README.md) | Azure Web App using Managed Identities |
3636
| [Web App and SQL Database ](./samples/web-app-sql-database/python/README.md) | Azure Web App using SQL Database |
37+
| [Web App and PostgreSQL Database ](./samples/web-app-postgresql-flexible-server/python/README.md) | Azure Web App using PostgreSQL Database |
3738
| [Web App with Custom Docker Image](./samples/web-app-custom-image/python/README.md) | Azure Web App running a custom Docker image |
3839
| [ACI and Blob Storage](./samples/aci-blob-storage/python/README.md) | Azure Container Instances with ACR, Key Vault, and Blob Storage |
3940
| [Azure Service Bus with Spring Boot](./samples/servicebus/java/README.md) | Azure Service Bus used by a Spring Boot application |
Binary file not shown.
31.7 KB
Binary file not shown.
31.7 KB
Binary file not shown.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Azure Web App with Azure Database for PostgreSQL flexible server
2+
3+
This sample demonstrates a Python Flask single-page web application called *Vacation Planner* hosted on an [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview). The app runs on an Azure App Service Plan and stores activity data in the `activities` table of the `PlannerDB` database on an [Azure Database for PostgreSQL flexible server](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/overview). The server is reached through a [Private Endpoint](https://learn.microsoft.com/azure/private-link/private-endpoint-overview) (group `postgresqlServer`) with the `privatelink.postgres.database.azure.com` Private DNS Zone, while a permissive server-level firewall rule lets the deploy machine run the post-create psql bootstrap that creates the application role and seeds the schema.
4+
5+
## Architecture
6+
7+
![Architecture Diagram](./images/architecture.png)
8+
9+
The web app enables users to plan and manage vacation activities; all data is persisted in PostgreSQL. The solution is composed of the following Azure resources:
10+
11+
1. [Azure Resource Group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-cli): A logical container scoping all resources in this sample.
12+
2. [Azure Virtual Network](https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview): Hosts two subnets:
13+
- *app-subnet*: Delegated to `Microsoft.Web/serverFarms` for regional VNet integration of the Web App.
14+
- *pe-subnet*: Hosts the Private Endpoint to the PostgreSQL flexible server.
15+
3. [Azure Private DNS Zone](https://learn.microsoft.com/azure/dns/private-dns-privatednszone) `privatelink.postgres.database.azure.com`, linked to the VNet. The Private Endpoint's DNS-zone group auto-registers the `A` record for the server, so the Web App resolves the server's private IP through the VNet.
16+
4. [Azure Private Endpoint](https://learn.microsoft.com/azure/private-link/private-endpoint-overview) (group `postgresqlServer`): Secures access to the PostgreSQL flexible server from the VNet.
17+
5. [Azure NAT Gateway](https://learn.microsoft.com/azure/nat-gateway/nat-overview): Deterministic outbound connectivity for both subnets.
18+
6. [Azure Network Security Group](https://learn.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview): One NSG per subnet.
19+
7. [Azure Log Analytics Workspace](https://learn.microsoft.com/azure/azure-monitor/logs/log-analytics-overview): Centralizes diagnostic logs and metrics.
20+
8. [Azure Database for PostgreSQL flexible server](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/overview): Public-access server hosting the `PlannerDB` database. Burstable `Standard_B1ms`, version 16, 32 GiB storage, 7-day backup retention, HA disabled. A permissive firewall rule (`0.0.0.0–255.255.255.255`) is created so the deploy machine can run the post-create psql bootstrap; the Web App itself reaches the server through the Private Endpoint.
21+
9. [PostgreSQL database](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-server-and-database) `PlannerDB`: Created at provisioning time; the post-deploy psql step creates the `activities` table and seeds three demo rows.
22+
10. [Azure App Service Plan](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans): The underlying compute tier that hosts the web application.
23+
11. [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview): Runs the Python Flask *Vacation Planner* app with regional VNet integration into *app-subnet*. The Web App connects to PostgreSQL using a dedicated application role (`testuser`) — the server-admin login is never used at runtime.
24+
12. [App Service Source Control](https://learn.microsoft.com/en-us/rest/api/appservice/web-apps/create-or-update-source-control?view=rest-appservice-2024-11-01): *(Optional)* Configures continuous deployment from a public GitHub repository.
25+
26+
The deploy scripts follow the same pattern as the sibling [`web-app-sql-database`](../../web-app-sql-database/python/) sample: after provisioning, they (i) connect as the server admin via the public endpoint + firewall rule, (ii) create the application role `testuser` with its own password, (iii) grant minimum schema privileges on `PlannerDB`, (iv) create the `activities` table, (v) seed three sample rows, and (vi) write `PG_USER=testuser` + `PG_PASSWORD` onto the Web App's app settings. The server-admin login is never written into the Web App's runtime configuration.
27+
28+
## Prerequisites
29+
30+
- [Azure Subscription](https://azure.microsoft.com/free/)
31+
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)
32+
- [Python 3.11+](https://www.python.org/downloads/)
33+
- [Flask](https://flask.palletsprojects.com/)
34+
- [psycopg2](https://www.psycopg.org/docs/) (`psycopg2-binary` for development)
35+
- [PostgreSQL client tools](https://www.postgresql.org/download/) (`psql`) — required by the deploy scripts to create the application role and seed data
36+
- [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep), if you plan to install the sample via Bicep
37+
- [Terraform](https://developer.hashicorp.com/terraform/downloads), if you plan to install the sample via Terraform
38+
39+
## Deployment
40+
41+
Set up the Azure emulator using the LocalStack for Azure Docker image. Before starting, ensure you have a valid `LOCALSTACK_AUTH_TOKEN`. Refer to the [Auth Token guide](https://docs.localstack.cloud/getting-started/auth-token/) to obtain yours. Pull and start the emulator:
42+
43+
```bash
44+
docker pull localstack/localstack-azure-alpha
45+
46+
export LOCALSTACK_AUTH_TOKEN=<your_auth_token>
47+
IMAGE_NAME=localstack/localstack-azure-alpha localstack start -d
48+
localstack wait -t 60
49+
50+
# Route all Azure CLI calls to the LocalStack Azure emulator
51+
azlocal start-interception
52+
```
53+
54+
Deploy the application using one of these methods:
55+
56+
- [Azure CLI Deployment](./scripts/README.md)
57+
- [Bicep Deployment](./bicep/README.md)
58+
- [Terraform Deployment](./terraform/README.md)
59+
60+
All three variants provision the same topology: VNet + pe-subnet hosting a Private Endpoint targeting a public-access PostgreSQL flexible server, with a Private DNS Zone linked to the VNet.
61+
62+
> **Note**
63+
> When you deploy the application to LocalStack for Azure for the first time, the initialization process pulls and builds Docker images (LocalStack itself plus the `postgres:18` backing container for the flexible-server emulator). This is a one-time operation — subsequent deployments are much faster.
64+
65+
## Test
66+
67+
1. Retrieve the port published and mapped to port 80 by the Docker container hosting the emulated Web App.
68+
2. Open a web browser and navigate to `http://localhost:<published-port>`.
69+
3. If the deployment was successful, you will see the *Vacation Planner* UI with the three seeded activities (*Go to Paris*, *Go to London*, *Go to Mexico*) and can add, edit, and remove activities.
70+
71+
![Vacation Planner UI](./images/vacation-planner.png)
72+
73+
You can use the `scripts/call-web-app.sh` Bash script to call the web app from outside the emulator. The script demonstrates four call paths:
74+
75+
1. **Through the LocalStack for Azure emulator** via the default hostname.
76+
2. **Via localhost and host port** mapped to the container's port `80`.
77+
3. **Via container IP address** on port `80`.
78+
4. **Via the default hostname** `<web-app-name>.azurewebsites.azure.localhost.localstack.cloud:4566`.
79+
80+
## PostgreSQL Tooling
81+
82+
You can use [pgAdmin](https://www.pgadmin.org/) to explore and manage the deployed database. Connect using:
83+
84+
| Field | Value |
85+
| -------- | --------------------------------------------------------------------------- |
86+
| Host | `localhost` |
87+
| Port | (see `docker ps` for the host-mapped port of the backing `postgres:18` container) |
88+
| Database | `PlannerDB` |
89+
| Username | `testuser` *(or `pgadmin` for admin operations)* |
90+
| Password | `TestP@ssw0rd123` *(or `P@ssw0rd1234!` for the admin)* |
91+
92+
Or use [psql](https://www.postgresql.org/docs/current/app-psql.html):
93+
94+
```bash
95+
PGPASSWORD='TestP@ssw0rd123' psql -h localhost -p <port> -U testuser -d PlannerDB
96+
PlannerDB=> SELECT id, username, activity, created_at FROM activities;
97+
```
98+
99+
## References
100+
101+
- [Azure Web Apps Documentation](https://learn.microsoft.com/en-us/azure/app-service/)
102+
- [Azure Database for PostgreSQL — flexible server](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/)
103+
- [Quickstart: Python Flask on Azure](https://learn.microsoft.com/en-us/azure/app-service/quickstart-python?tabs=flask%2Cbrowser)
104+
- [psycopg2 documentation](https://www.psycopg.org/docs/)
105+
- [LocalStack for Azure](https://docs.localstack.cloud/azure/)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Bicep Deployment
2+
3+
This directory contains the Bicep template and a deployment script for provisioning the sample's Azure resources. For details about the sample application, see [Azure Web App with Azure Database for PostgreSQL flexible server](../README.md).
4+
5+
## Prerequisites
6+
7+
- [LocalStack for Azure](https://docs.localstack.cloud/azure/)
8+
- [Visual Studio Code](https://code.visualstudio.com/) + [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep)
9+
- [Docker](https://docs.docker.com/get-docker/)
10+
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) + [Azlocal CLI](https://azure.localstack.cloud/user-guides/sdks/az/)
11+
- [Python 3.12+](https://www.python.org/downloads/)
12+
- [PostgreSQL client (`psql`)](https://www.postgresql.org/download/)
13+
- [`jq`](https://jqlang.org/)
14+
15+
```bash
16+
pip install azlocal
17+
```
18+
19+
## Architecture Overview
20+
21+
The [`deploy.sh`](deploy.sh) script creates the resource group while the Bicep modules create:
22+
23+
1. [Azure Virtual Network](https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview) with two subnets:
24+
- *app-subnet*: delegated to `Microsoft.Web/serverFarms` for the Web App's regional VNet integration.
25+
- *pe-subnet*: hosts the Private Endpoint to the PostgreSQL flexible server.
26+
2. [Azure Private DNS Zone](https://learn.microsoft.com/azure/dns/private-dns-privatednszone) `privatelink.postgres.database.azure.com`, linked to the VNet.
27+
3. [Azure Private Endpoint](https://learn.microsoft.com/azure/private-link/private-endpoint-overview) (group `postgresqlServer`).
28+
4. [Azure NAT Gateway](https://learn.microsoft.com/azure/nat-gateway/nat-overview).
29+
5. [Network Security Groups](https://learn.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview): one per subnet.
30+
6. [Azure Log Analytics Workspace](https://learn.microsoft.com/azure/azure-monitor/logs/log-analytics-overview).
31+
7. [Azure Database for PostgreSQL flexible server](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/overview): public-access mode, Burstable `Standard_B1ms`, version 16, 32 GiB, HA disabled. A permissive firewall rule (`0.0.0.0–255.255.255.255`) lets the deploy machine reach the server for the post-create psql bootstrap.
32+
8. [PostgreSQL database](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-server-and-database) `sampledb` (UTF8 / `en_US.utf8`).
33+
9. [Azure App Service Plan](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans).
34+
10. [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview) with regional VNet integration into *app-subnet*. The Bicep template sets `PG_HOST`, `PG_PORT`, and `PG_DATABASE` on the Web App but **does not** set `PG_USER` or `PG_PASSWORD` — those are written by `deploy.sh` after psql creates the application role.
35+
36+
## Configuration
37+
38+
Update [`main.bicepparam`](main.bicepparam) before deploying. The defaults are:
39+
40+
```bicep
41+
using 'main.bicep'
42+
43+
param prefix = 'local'
44+
param suffix = 'test'
45+
param runtimeName = 'python'
46+
param runtimeVersion = '3.13'
47+
param databaseName = 'sampledb'
48+
param username = 'paolo'
49+
50+
param pgAdminLogin = 'pgadmin'
51+
param pgAdminPassword = readEnvironmentVariable('PG_ADMIN_PASSWORD', '')
52+
param pgVersion = '16'
53+
param pgSkuTier = 'Burstable'
54+
param pgSkuName = 'Standard_B1ms'
55+
param pgStorageSizeGB = 32
56+
param pgBackupRetentionDays = 7
57+
```
58+
59+
`pgAdminPassword` is read from the `PG_ADMIN_PASSWORD` env var. `deploy.sh` sets a default (`P@ssw0rd1234!`) if not provided; override for non-dev deployments.
60+
61+
## Deployment
62+
63+
```bash
64+
# default values
65+
bash deploy.sh
66+
67+
# override admin and app-user secrets
68+
PG_ADMIN_PASSWORD='<your-admin-password>' \
69+
PG_APP_PASSWORD='<your-app-password>' \
70+
bash deploy.sh
71+
```
72+
73+
The script will:
74+
75+
1. Ensure the resource group exists.
76+
2. Validate `main.bicep`.
77+
3. Deploy the template, passing `pgAdminPassword`.
78+
4. Use `psql` (connected via the public endpoint + firewall rule) to create the `testuser` role, the `activities` table, and the three demo rows.
79+
5. Set the Web App's `PG_USER`/`PG_PASSWORD` to `testuser` / `<app-password>` — the server admin login is never written to the Web App.
80+
6. Zip the application source under `../src` and deploy it.
81+
82+
## Verification
83+
84+
```bash
85+
PGPASSWORD='TestP@ssw0rd123' psql -h <fqdn> -p <port> -U testuser -d PlannerDB \
86+
-c "SELECT id, username, activity, created_at FROM activities;"
87+
```
88+
89+
`<port>` is `5432` in real Azure, or the port suffix of the server's FQDN in LocalStack:
90+
91+
```bash
92+
az postgres flexible-server show \
93+
--resource-group local-rg --name local-pgflex-test \
94+
--query fullyQualifiedDomainName --output tsv
95+
```

0 commit comments

Comments
 (0)