Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
*.yml text eol=lf
*.bicep text eol=lf
*.tf text eol=lf
*.cs text eol=lf
*.csproj text eol=lf
*.cshtml text eol=lf
58 changes: 56 additions & 2 deletions .github/workflows/run-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ jobs:
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
# A sample takes ~10-25 minutes; the bound covers a retry and stops a hung deployment from
# holding a runner for GitHub's 6-hour default.
timeout-minutes: 90
# ubuntu-22.04 for amd64, ubuntu-22.04-arm for arm64 — the matrix carries the label.
# GitHub-hosted arm64 runners are free for public repositories.
runs-on: ${{ matrix.runner }}
Expand Down Expand Up @@ -191,10 +194,30 @@ jobs:
# I/O pushed some starts past a 120s budget - and, on the slowest runners,
# occasionally past 300s as well. A healthy emulator still returns as soon as
# it is ready, so a larger timeout costs nothing on the happy path.
# A start that never becomes ready is retried on a fresh container (see below).
run: |
source .venv/bin/activate
python -m localstack_cli.cli.main start -d
python -m localstack_cli.cli.main wait -t 600
# Even at 600s the wait occasionally expires: ~80 concurrent jobs compete for runner I/O
# and image pulls, and a start that stalls failed the job before the sample ever ran.
# Give it one clean retry, and print the container's logs when the first attempt expires.
# A healthy start reports ready in well under a minute, so a wait that reaches the
# timeout means the boot has stalled rather than slowed: observed on an arm64 runner,
# where the container logged nothing for the last ten minutes of the wait. Three shorter
# attempts therefore beat two long ones - same overall budget, one more chance, and a
# stalled container is replaced sooner.
attempts=3
for attempt in $(seq 1 "${attempts}"); do
python -m localstack_cli.cli.main start -d
if python -m localstack_cli.cli.main wait -t 400; then
echo "Emulator ready (attempt ${attempt}/${attempts})."
exit 0
fi
echo "::warning::Emulator was not ready within 400s (attempt ${attempt}/${attempts})."
docker logs localstack-main --tail 100 2>&1 || true
python -m localstack_cli.cli.main stop || true
done
echo "::error::Emulator failed to become ready after ${attempts} attempts."
exit 1
env:
IMAGE_NAME: ${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}
LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }}
Expand Down Expand Up @@ -260,6 +283,12 @@ jobs:
- name: "Run: ${{ matrix.name }}"
# Each job runs exactly one test. SPLITS equals the total test count, and SHARD
# is the 1-based index of this specific test, so run-samples.sh executes only it.
id: initial_run
# Samples provision real containers through the emulator, so a single transient error (a 500
# from a provisioning call, a slow container start) fails the whole sample - which is what
# has been needing manual job re-runs. The retry step below decides this job's outcome, so a
# genuinely broken sample still fails: it fails both attempts.
continue-on-error: true
run: make test SHARD="${SHARD}" SPLITS="${SPLITS}"
env:
# Dynamic matrix values routed via env, not interpolated into the shell (template-injection)
Expand All @@ -268,6 +297,31 @@ jobs:
LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }}
PURGE_DOCKER: "1"

- name: "Retry: ${{ matrix.name }}"
# The deploy scripts are not idempotent - they fail on resources that already exist - so the
# retry runs against a fresh emulator instead of the half-provisioned state the failed
# attempt left behind.
if: steps.initial_run.outcome == 'failure'
run: |
source .venv/bin/activate
echo "::warning::${MATRIX_NAME} failed once; retrying on a fresh emulator."
python -m localstack_cli.cli.main stop || true
python -m localstack_cli.cli.main start -d
python -m localstack_cli.cli.main wait -t 600
make test SHARD="${SHARD}" SPLITS="${SPLITS}"
env:
MATRIX_NAME: ${{ matrix.name }}
SHARD: ${{ matrix.shard }}
SPLITS: ${{ matrix.splits }}
IMAGE_NAME: ${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}
LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }}
DOCKER_FLAGS: "-e MSSQL_ACCEPT_EULA=Y -e GITHUB_API_TOKEN=${{ github.token }}"
LS_LOG: "DEBUG"
DISABLE_EVENTS: "1"
ACTIVATE_PRO: "1"
DNS_ADDRESS: "0"
PURGE_DOCKER: "1"

- name: Get LocalStack Logs
# Captured on failure or success to provide a detailed audit trail of the emulator's activity.
if: always()
Expand Down
42 changes: 22 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,28 @@ This repository contains comprehensive sample projects demonstrating how to deve

## Outline

| Sample Name | Description |
|-------------|-------------|
| [Function App and Storage](./samples/function-app-storage-http/dotnet/README.md) | Azure Functions App using Blob, Queue, and Table Storage |
| [Function App and Front Door](./samples/function-app-front-door/python/README.md) | Azure Functions App exposed via Front Door |
| [Function App and Managed Identities](./samples/function-app-managed-identity/python/README.md) | Azure Function App using Managed Identities |
| [Function App and Service Bus](./samples/function-app-service-bus/dotnet/README.md) | Azure Function App using Service Bus |
| [Web App and CosmosDB for MongoDB API ](./samples/web-app-cosmosdb-mongodb-api/python/README.md) | Azure Web App using CosmosDB for MongoDB API |
| [Web App and CosmosDB for NoSQL API ](./samples/web-app-cosmosdb-nosql-api/python/README.md) | Azure Web App using CosmosDB for NoSQL API |
| [Web App and Managed Identities](./samples/web-app-managed-identity/python/README.md) | Azure Web App using Managed Identities |
| [Web App and SQL Database ](./samples/web-app-sql-database/python/README.md) | Azure Web App using SQL Database |
| [Web App and PostgreSQL Database ](./samples/web-app-postgresql-flexible-server/python/README.md) | Azure Web App using PostgreSQL Database |
| [Web App and MySQL Database ](./samples/web-app-mysql-flexible-server/python/README.md) | Azure Web App using MySQL Database |
| [Web App with Custom Docker Image](./samples/web-app-custom-image/python/README.md) | Azure Web App running a custom Docker image |
| [ACI and Blob Storage](./samples/aci-blob-storage/python/README.md) | Azure Container Instances with ACR, Key Vault, and Blob Storage |
| [Container Apps and Blob Storage](./samples/container-apps-blob-storage/python/README.md) | Azure Container Apps running a guestbook app from ACR with Blob Storage, secrets, revisions, replicas and scale rules |
| [Azure Service Bus with Spring Boot](./samples/servicebus/java/README.md) | Azure Service Bus used by a Spring Boot application |
| [URL Shortener](./samples/url-shortener/python/README.md) | URL shortener composing Web App, Functions, Storage, Key Vault, Service Bus and PostgreSQL |
| [Event Hubs Fraud Detection Pipeline](./samples/eventhubs/python/README.md) | Real-time payment stream processing with Event Hubs (AMQP, Kafka and HTTPS ingestion, Capture, Schema Registry), an Event Hubs-triggered Function App, Key Vault, Storage and a Web App dashboard |
| [Event Hubs Cold-Path Automation](./samples/eventhubs-eventgrid/python/README.md) | Event Hubs Capture raises `Microsoft.EventHub.CaptureFileCreated` to an Event Grid system topic, a subscription delivers it into a second event hub, and an Event Hubs-triggered Function App decodes each Avro archive and writes per-device summaries |

Each sample is a self-contained project with its own README, Azure CLI scripts and, where applicable, Bicep and Terraform deployments that target both real Azure and the LocalStack for Azure emulator. The language of each implementation is shown in parentheses; the web-app samples share the same *Vacation Planner* application and come in a Python (Flask) and a .NET 10 (ASP.NET Core Razor Pages) version.

| Sample | Description |
|--------|-------------|
| [Function App and Storage (.NET)](./samples/function-app-storage-http/dotnet/README.md) | A gaming scoreboard built on Azure Functions (isolated worker): HTTP triggers record player scores in Table Storage, publish messages to Queue Storage and write game-session summaries to Blob Storage, all against the emulated storage account. |
| [Function App and Front Door (Python)](./samples/function-app-front-door/python/README.md) | A minimal Python Function App answering `/{name}`, published behind an Azure Front Door (Standard) profile so requests reach the function through the Front Door endpoint; deployable to real Azure or to the emulator. |
| [Function App and Managed Identities (Python)](./samples/function-app-managed-identity/python/README.md) | A serverless text processor: an Azure Functions app reads text blobs from an `input` container, converts them to uppercase and writes the result to an `output` container, authenticating to the storage account with a managed identity instead of keys. |
| [Function App and Service Bus (.NET)](./samples/function-app-service-bus/dotnet/README.md) | An Azure Functions app on an App Service plan that exchanges messages through Service Bus queues: an HTTP trigger sends greetings and a queue trigger consumes them, connecting with either a connection string or a managed identity. |
| Web App and CosmosDB for MongoDB API ([Python](./samples/web-app-cosmosdb-mongodb-api/python/README.md), [.NET](./samples/web-app-cosmosdb-mongodb-api/dotnet/README.md)) | The *Vacation Planner* single-page web app on an Azure Web App with regional VNet integration, storing activities in the `activities` collection of an Azure Cosmos DB for MongoDB account reached through a private endpoint. |
| Web App and CosmosDB for NoSQL API ([Python](./samples/web-app-cosmosdb-nosql-api/python/README.md), [.NET](./samples/web-app-cosmosdb-nosql-api/dotnet/README.md)) | The *Vacation Planner* single-page web app on an Azure Web App, storing activities as JSON items in the `activities` container of an Azure Cosmos DB for NoSQL database partitioned by user; deployed with Azure CLI scripts. |
| Web App and Managed Identities ([Python](./samples/web-app-managed-identity/python/README.md), [.NET](./samples/web-app-managed-identity/dotnet/README.md)) | The *Vacation Planner* single-page web app on an Azure Web App, storing each activity as a blob in an `activities` container and accessing Blob Storage through a user-assigned or system-assigned managed identity rather than connection strings. |
| Web App and SQL Database ([Python](./samples/web-app-sql-database/python/README.md), [.NET](./samples/web-app-sql-database/dotnet/README.md)) | The *Vacation Planner* single-page web app on an Azure Web App, storing activities in an Azure SQL Database. The connection string and the HTTPS certificate are read from Azure Key Vault, and an API endpoint verifies the Key Vault certificate. |
| Web App and PostgreSQL Database ([Python](./samples/web-app-postgresql-flexible-server/python/README.md), [.NET](./samples/web-app-postgresql-flexible-server/dotnet/README.md)) | The *Vacation Planner* single-page web app on an Azure Web App, storing activities in an Azure Database for PostgreSQL flexible server injected into a virtual network (delegated subnet and private DNS zone), with the database and application user created by the deploy scripts. |
| Web App and MySQL Database ([Python](./samples/web-app-mysql-flexible-server/python/README.md), [.NET](./samples/web-app-mysql-flexible-server/dotnet/README.md)) | The *Vacation Planner* single-page web app on an Azure Web App, storing activities in an Azure Database for MySQL flexible server injected into a virtual network, using TLS-only connections, with the database and application user created by the deploy scripts. |
| Web App with Custom Docker Image ([Python](./samples/web-app-custom-image/python/README.md), [.NET](./samples/web-app-custom-image/dotnet/README.md)) | A web app that runs a custom container image built locally and pushed to Azure Container Registry; the web app pulls it with a managed identity (AcrPull) through a VNet-integrated network and reports its image and host name on `/api/status`. |
| [ACI and Blob Storage (Python)](./samples/aci-blob-storage/python/README.md) | A containerized Flask web app on Azure Container Instances, with its image in Azure Container Registry, its secrets in Key Vault and its data in Blob Storage. |
| [Container Apps and Blob Storage (Python)](./samples/container-apps-blob-storage/python/README.md) | A guestbook web app on Azure Container Apps pulled from Azure Container Registry that persists entries in Blob Storage and exercises secrets, revisions, replicas and scale rules. |
| [Azure Service Bus with Spring Boot (Java)](./samples/servicebus/java/README.md) | A Java Spring Boot application that sends and receives Service Bus messages through the Spring Cloud Azure Service Bus Stream Binder. |
| [URL Shortener (Python)](./samples/url-shortener/python/README.md) | *Linklet*, a Flask URL shortener on an Azure Web App with an event-driven Azure Functions worker, composing Web App, Functions, Storage, Key Vault, Service Bus and PostgreSQL into a single causal chain. |
| [Event Hubs Fraud Detection Pipeline (Python)](./samples/eventhubs/python/README.md) | A streaming platform in miniature: payments ingested into Event Hubs over AMQP, Kafka and HTTPS, validated against Schema Registry, archived with Capture, scored by an Event Hubs-triggered Function App and shown on a Web App dashboard, with secrets in Key Vault. |
| [Event Hubs Cold-Path Automation (Python)](./samples/eventhubs-eventgrid/python/README.md) | A cold-chain monitoring pipeline in which Event Hubs Capture raises `Microsoft.EventHub.CaptureFileCreated` events to an Event Grid system topic; a subscription delivers them into a second event hub and an Event Hubs-triggered Function App decodes each Avro archive into per-device summaries. |

## Sample Structure

Each sample project is organized by Azure service and includes:
Expand Down
19 changes: 18 additions & 1 deletion run-samples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,18 @@ SAMPLES=(
"samples/function-app-service-bus/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-http-trigger.sh"
"samples/function-app-storage-http/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-http-triggers.sh"
"samples/web-app-cosmosdb-mongodb-api/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-cosmosdb-mongodb-api/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-cosmosdb-nosql-api/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-managed-identity/python|bash scripts/user-assigned.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-sql-database/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/get-web-app-url.sh"
"samples/web-app-managed-identity/dotnet|bash scripts/user-assigned.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-sql-database/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-sql-database/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-mysql-flexible-server/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-mysql-flexible-server/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-postgresql-flexible-server/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-postgresql-flexible-server/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-custom-image/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/web-app-custom-image/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
"samples/aci-blob-storage/python|bash scripts/deploy.sh|bash scripts/validate.sh"
"samples/container-apps-blob-storage/python|bash scripts/deploy.sh|bash scripts/validate.sh"
"samples/url-shortener/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh"
Expand All @@ -56,10 +63,15 @@ TERRAFORM_SAMPLES=(
"samples/function-app-service-bus/dotnet/terraform|bash deploy.sh"
"samples/function-app-storage-http/dotnet/terraform|bash deploy.sh"
"samples/web-app-cosmosdb-mongodb-api/python/terraform|bash deploy.sh"
"samples/web-app-cosmosdb-mongodb-api/dotnet/terraform|bash deploy.sh"
"samples/web-app-managed-identity/python/terraform|bash deploy.sh"
"samples/web-app-managed-identity/dotnet/terraform|bash deploy.sh"
"samples/web-app-sql-database/python/terraform|bash deploy.sh"
"samples/web-app-sql-database/dotnet/terraform|bash deploy.sh"
"samples/web-app-mysql-flexible-server/python/terraform|bash deploy.sh"
"samples/web-app-mysql-flexible-server/dotnet/terraform|bash deploy.sh"
"samples/web-app-postgresql-flexible-server/python/terraform|bash deploy.sh"
"samples/web-app-postgresql-flexible-server/dotnet/terraform|bash deploy.sh"
"samples/aci-blob-storage/python/terraform|bash deploy.sh"
"samples/container-apps-blob-storage/python/terraform|bash deploy.sh"
"samples/url-shortener/python/terraform|bash deploy.sh|bash ../scripts/validate.sh"
Expand All @@ -75,9 +87,13 @@ BICEP_SAMPLES=(
"samples/function-app-service-bus/dotnet/bicep|bash deploy.sh"
"samples/function-app-storage-http/dotnet/bicep|bash deploy.sh"
"samples/web-app-cosmosdb-mongodb-api/python/bicep|bash deploy.sh"
"samples/web-app-cosmosdb-mongodb-api/dotnet/bicep|bash deploy.sh"
"samples/web-app-managed-identity/python/bicep|bash deploy.sh"
"samples/web-app-managed-identity/dotnet/bicep|bash deploy.sh"
"samples/web-app-mysql-flexible-server/python/bicep|bash deploy.sh"
"samples/web-app-mysql-flexible-server/dotnet/bicep|bash deploy.sh"
"samples/web-app-postgresql-flexible-server/python/bicep|bash deploy.sh"
"samples/web-app-postgresql-flexible-server/dotnet/bicep|bash deploy.sh"
"samples/aci-blob-storage/python/bicep|bash deploy.sh"
"samples/container-apps-blob-storage/python/bicep|bash deploy.sh"
"samples/url-shortener/python/bicep|bash deploy.sh|bash ../scripts/validate.sh"
Expand Down Expand Up @@ -122,6 +138,7 @@ ARM64_SAMPLE_DIRS=(
"samples/function-app-service-bus/dotnet"
"samples/function-app-storage-http/dotnet"
"samples/web-app-custom-image/python"
"samples/web-app-custom-image/dotnet"
)

# Fail loudly if an entry no longer matches a registered sample: a rename would otherwise
Expand Down
2 changes: 1 addition & 1 deletion samples/aci-blob-storage/python/terraform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ provider "azurerm" {
prevent_deletion_if_contains_resources = false
}
}
metadata_host="localhost.localstack.cloud:4566"
metadata_host="azure.localhost.localstack.cloud:4566"
subscription_id = "00000000-0000-0000-0000-000000000000"
}
```
Expand Down
2 changes: 1 addition & 1 deletion samples/aci-blob-storage/python/terraform/providers.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ provider "azurerm" {
# Set the hostname of the Azure Metadata Service (for example management.azure.com)
# used to obtain the Cloud Environment when using LocalStack's Azure emulator.
# This allows the provider to correctly identify the environment and avoid making calls to the real Azure endpoints.
metadata_host = "localhost.localstack.cloud:4566"
metadata_host = "azure.localhost.localstack.cloud:4566"

# Set the subscription ID to a dummy value when using LocalStack's Azure emulator.
subscription_id = "00000000-0000-0000-0000-000000000000"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ provider "azurerm" {
prevent_deletion_if_contains_resources = false
}
}
metadata_host="localhost.localstack.cloud:4566"
metadata_host="azure.localhost.localstack.cloud:4566"
subscription_id = "00000000-0000-0000-0000-000000000000"
}
```
Expand Down
Loading
Loading