Skip to content

Latest commit

 

History

History
160 lines (121 loc) · 10.6 KB

File metadata and controls

160 lines (121 loc) · 10.6 KB

URL Shortener: Web App, Functions, Storage, Key Vault, Service Bus and PostgreSQL

This sample demonstrates Linklet, a Python Flask URL shortener hosted on an Azure Web App with an event-driven worker on Azure Functions. Unlike the sibling samples, which each exercise one or two services, this sample composes six Azure services into a single causal chain: every shortened link fans out through Azure Service Bus and Azure Queue Storage to background workers, and every redirect is logged to an Azure Database for PostgreSQL flexible server. It is intended as a realistic end-to-end workout for the LocalStack Azure emulator: a regression in any one service breaks an observable user outcome.

Architecture

The solution is composed of the following Azure resources:

  1. Azure Resource Group: A logical container scoping all resources in this sample.
  2. Azure User-Assigned Managed Identity: Shared by the web app and the worker; all Storage and Key Vault data-plane calls are credential-free through it.
  3. Azure Storage Account with three data planes:
    • links Table: The link store (code, target URL, hit counter, signature, scan verdict, QR status).
    • qrjobs Queue: QR-render jobs consumed by the worker's queue trigger.
    • qrcodes Blob container (public read): The rendered QR SVGs.
  4. Azure Key Vault (RBAC mode): Holds the HMAC key that signs every short code and the PostgreSQL connection string; the app reads both at runtime through the managed identity.
  5. Azure Database for PostgreSQL flexible server: The clicks database receives one row per redirect (burstable Standard_B1ms, version 16).
  6. Azure Service Bus (Standard): The link-events queue carries link-created events to the abuse-scan worker.
  7. Azure Log Analytics Workspace: Receives the storage account's transaction metrics through diagnostic settings.
  8. Azure App Service Plan (Linux, B1): Hosts both compute components.
  9. Azure Web App: The Flask UI/API. POST /shorten writes the link, signs the code with the Key Vault key, sends a Service Bus event and enqueues a QR job; GET /l/<code> bumps the hit counter, logs the click to PostgreSQL and redirects.
  10. Azure Function App (Python v2 model): Two event-driven workers that call back into the web app's token-protected internal API:
    • AbuseScan (Service Bus trigger, identity-based connection): Applies a keyword heuristic and reports a clean/flagged verdict.
    • QrGenerator (Queue Storage trigger): Requests the QR SVG render for the short link.
flowchart LR
    user((User))

    subgraph webapp["Web App (Flask)"]
        shorten["POST /shorten"]
        follow["GET /l/{code}"]
        internal["POST /internal/*<br/>(token-protected)"]
    end

    subgraph functions["Function App (worker)"]
        abuse["AbuseScan<br/>(Service Bus trigger)"]
        qrgen["QrGenerator<br/>(queue trigger)"]
    end

    subgraph storage["Storage Account"]
        links[("links table")]
        qrjobs[["qrjobs queue"]]
        qrcodes[("qrcodes container<br/>public read")]
    end

    kv["Key Vault<br/>link-sign-key, pg-conn"]
    sb[["Service Bus<br/>link-events queue"]]
    pg[("PostgreSQL<br/>clicks db")]
    logs["Log Analytics"]

    user -->|"1: shorten URL"| shorten
    shorten -.->|"read sign key"| kv
    shorten -->|"write link + sig"| links
    shorten -->|"link-created event"| sb
    shorten -->|"QR job"| qrjobs

    sb -->|"trigger"| abuse
    qrjobs -->|"trigger"| qrgen
    abuse -->|"verdict"| internal
    qrgen -->|"render request"| internal
    internal -->|"scan / qr status"| links
    internal -->|"QR SVG"| qrcodes

    user -->|"2: follow short link"| follow
    follow -->|"hit counter"| links
    follow -.->|"read pg-conn"| kv
    follow -->|"click row"| pg

    user -.->|"3: fetch QR"| qrcodes
    storage -.->|"transaction metrics"| logs
Loading

The flow of a single link: POST /shorten → Table Storage + Key Vault + Service Bus + Queue Storage → workers → internal API → Table Storage + Blob Storage → GET /l/<code> → PostgreSQL + 302 redirect. The home page renders the link table with hit counts, signatures, scan verdicts and QR links.

Prerequisites

Setup

Start the LocalStack Azure emulator and route the Azure CLI to it:

export LOCALSTACK_AUTH_TOKEN=<your_auth_token>
IMAGE_NAME=localstack/localstack-azure localstack start -d
localstack wait -t 60
lstk az start-interception
az login --service-principal -u any-app -p any-pass --tenant any-tenant

Deployment

Azure CLI scripts

bash scripts/deploy.sh

The script provisions all resources idempotently, deploys both applications from zip packages and persists the generated PostgreSQL credentials to scripts/.last_deploy.env for the validation script.

Terraform

cd terraform
bash deploy.sh

The Terraform variant provisions the same resources declaratively and then performs the two zip deployments with the Azure CLI. It also persists the generated PostgreSQL credentials to scripts/.last_deploy.env for the validation script.

Bicep

cd bicep
bash deploy.sh

The Bicep variant validates and deploys main.bicep into the resource group and then performs the two zip deployments with the Azure CLI. It also persists the generated PostgreSQL credentials to scripts/.last_deploy.env for the validation script.

Testing

bash scripts/validate.sh
bash scripts/call-web-app.sh

validate.sh walks the full causal chain: web app health (Table Storage + Key Vault + PostgreSQL through the managed identity), shortening a benign and a suspicious URL, the AbuseScan verdicts arriving via the Service Bus trigger, the QR SVG rendered via the queue trigger and served from the public blob container, redirects logging click rows into PostgreSQL, hit counting in Table Storage, and the Log Analytics wiring. call-web-app.sh performs a quick user-level smoke test (home page, shorten, redirect).

You can also open the web app in a browser — the URL is printed at the end of the deploy script.

Cleanup

az group delete --name local-rg --yes

LocalStack notes

  • The web app talks to Service Bus through the namespace connection string rather than a managed-identity connection: the Python Service Bus SDK enforces TLS verification and the emulator's certificate does not cover *.servicebus.windows.net. The connection string's endpoint is certificate-valid on both the emulator and real Azure.
  • The worker's queue trigger uses a dedicated connection string with explicit BlobEndpoint/QueueEndpoint/TableEndpoint entries because strict .NET storage clients cannot parse an EndpointSuffix that carries the emulator's port.
  • The PostgreSQL flexible-server emulator embeds its TCP-proxy port in the server's fullyQualifiedDomainName; both deployment variants split host and port so the same configuration works against real Azure (bare host, port 5432).
  • The worker calls the web app's internal API over plain http:// (WEB_BASE_URL), and the apps are deployed without HTTPS-only: the emulator serves *.azurewebsites.net with a certificate that does not cover that domain, so an https:// call from the worker would fail TLS verification. On real Azure, switch WEB_BASE_URL to https:// and enable HTTPS-only on both apps so the internal token never crosses the wire unencrypted.
  • On real Azure, additionally enable Always On for the function app (dedicated plans idle otherwise and non-HTTP triggers stop firing), deploy the function app through a build-enabled path (Oryx remote build or a vendored .python_packages layout), and tighten the sample's allow-all PostgreSQL firewall rule to your own address ranges.

References