-
Notifications
You must be signed in to change notification settings - Fork 0
Add multi-service-app sample: URL shortener composing seven Azure services #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
lazarkanelov
wants to merge
8
commits into
main
Choose a base branch
from
add-multi-service-app-sample
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
d347732
Add multi-service-app sample: URL shortener composing seven Azure ser…
lazarkanelov 9fbd881
Ignore generated .last_deploy state files
lazarkanelov d51e05f
Tolerate gateway-followed redirects from pre-fix emulator releases
lazarkanelov c479a5c
Pin lstk CLI to 0.19.0 to restore emulator detection in CI
lazarkanelov dd83fc5
Merge remote-tracking branch 'origin/main' into add-multi-service-app…
lazarkanelov 62337ac
Rename multi-service-app sample to url-shortener
lazarkanelov 75f29f1
Address code review feedback on the url-shortener sample
lazarkanelov c03be72
Add a Mermaid architecture diagram to the url-shortener README
lazarkanelov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -183,6 +183,7 @@ celerybeat.pid | |
| .env | ||
| .env.* | ||
| !.env.example | ||
| .last_deploy*.env | ||
| .venv | ||
| env/ | ||
| venv/ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| # 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](https://learn.microsoft.com/en-us/azure/app-service/overview) with an event-driven worker on [Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview). 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](https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview) and [Azure Queue Storage](https://learn.microsoft.com/en-us/azure/storage/queues/storage-queues-introduction) to background workers, and every redirect is logged to an [Azure Database for PostgreSQL flexible server](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/overview). 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](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-cli): A logical container scoping all resources in this sample. | ||
| 2. [Azure User-Assigned Managed Identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview): 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](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-overview) with three data planes: | ||
| - *links* [Table](https://learn.microsoft.com/en-us/azure/storage/tables/table-storage-overview): The link store (code, target URL, hit counter, signature, scan verdict, QR status). | ||
| - *qrjobs* [Queue](https://learn.microsoft.com/en-us/azure/storage/queues/storage-queues-introduction): QR-render jobs consumed by the worker's queue trigger. | ||
| - *qrcodes* [Blob container](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction) (public read): The rendered QR SVGs. | ||
| 4. [Azure Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/general/overview) (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](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/overview): The `clicks` database receives one row per redirect (burstable `Standard_B1ms`, version 16). | ||
| 6. [Azure Service Bus](https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview) (Standard): The *link-events* queue carries link-created events to the abuse-scan worker. | ||
| 7. [Azure Log Analytics Workspace](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-overview): Receives the storage account's transaction metrics through diagnostic settings. | ||
| 8. [Azure App Service Plan](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans) (Linux, B1): Hosts both compute components. | ||
| 9. [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview): 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](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview) (Python v2 model): Two event-driven workers that call back into the web app's token-protected internal API: | ||
| - *AbuseScan* ([Service Bus trigger](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-trigger), identity-based connection): Applies a keyword heuristic and reports a `clean`/`flagged` verdict. | ||
| - *QrGenerator* ([Queue Storage trigger](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger)): Requests the QR SVG render for the short link. | ||
|
|
||
| ```mermaid | ||
| 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 | ||
| ``` | ||
|
|
||
| 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 | ||
|
|
||
| - [Docker](https://docs.docker.com/get-docker/) | ||
| - [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) | ||
| - [lstk CLI](https://docs.localstack.cloud/aws/developer-tools/running-localstack/lstk/) | ||
| - [jq](https://jqlang.org/) and `zip` | ||
| - [psql](https://www.postgresql.org/docs/current/app-psql.html) (PostgreSQL client, used by the validation script) | ||
| - [Terraform](https://developer.hashicorp.com/terraform/downloads) (for the Terraform deployment) | ||
| - [Bicep](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install) (for the Bicep deployment) | ||
| - A LocalStack account with a valid `LOCALSTACK_AUTH_TOKEN` (see the [Auth Token guide](https://docs.localstack.cloud/getting-started/auth-token/)) | ||
|
|
||
| ## Setup | ||
|
|
||
| Start the LocalStack Azure emulator and route the Azure CLI to it: | ||
|
|
||
| ```bash | ||
| 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 | ||
| 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 | ||
|
|
||
| ```bash | ||
| 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 | ||
|
|
||
| ```bash | ||
| 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 | ||
| 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 | ||
|
|
||
| ```bash | ||
| az group delete --name local-rg --yes | ||
| ``` | ||
|
|
||
| ## LocalStack notes | ||
|
lazarkanelov marked this conversation as resolved.
|
||
|
|
||
| - 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 | ||
|
|
||
| - [Azure Web Apps Documentation](https://learn.microsoft.com/en-us/azure/app-service/) | ||
| - [Azure Functions Documentation](https://learn.microsoft.com/en-us/azure/azure-functions/) | ||
| - [Azure Service Bus Documentation](https://learn.microsoft.com/en-us/azure/service-bus-messaging/) | ||
| - [Azure Storage Documentation](https://learn.microsoft.com/en-us/azure/storage/) | ||
| - [Azure Key Vault Documentation](https://learn.microsoft.com/en-us/azure/key-vault/) | ||
| - [Azure Database for PostgreSQL — flexible server](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/) | ||
| - [LocalStack for Azure](https://docs.localstack.cloud/azure/) | ||
| - [lstk CLI](https://docs.localstack.cloud/aws/developer-tools/running-localstack/lstk/) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # Bicep Deployment | ||
|
|
||
| This directory contains the Bicep template for the sample. For details about the sample application, see [URL Shortener](../README.md). | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - [LocalStack for Azure](https://docs.localstack.cloud/azure/) | ||
| - [Docker](https://docs.docker.com/get-docker/) | ||
| - [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) with [Bicep](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install) | ||
| - [lstk CLI](https://docs.localstack.cloud/aws/developer-tools/running-localstack/lstk/) | ||
| - [jq](https://jqlang.org/), `zip` and `openssl` | ||
|
|
||
| ## Deployment | ||
|
|
||
| ```bash | ||
| bash deploy.sh | ||
| ``` | ||
|
|
||
| The script creates the resource group, validates and deploys `main.bicep` (generating the PostgreSQL password, the link-signing key and the internal API token per run), and then deploys the web app and the worker from zip packages with the Azure CLI. It persists the generated PostgreSQL credentials to `../scripts/.last_deploy.env` so `scripts/validate.sh` works after a Bicep deployment. | ||
|
|
||
| ## Cleanup | ||
|
|
||
| ```bash | ||
| az group delete --name local-rg --yes | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| #!/bin/bash | ||
|
|
||
| # Variables | ||
| PREFIX='local' | ||
| SUFFIX='test' | ||
| LOCATION='westeurope' | ||
| RESOURCE_GROUP_NAME="${PREFIX}-rg" | ||
| DEPLOYMENT_NAME='url-shortener' | ||
| TEMPLATE="main.bicep" | ||
| PARAMETERS="main.bicepparam" | ||
| WEBAPP_ZIPFILE='linklet_webapp.zip' | ||
| WORKER_ZIPFILE='linklet_worker.zip' | ||
| CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" | ||
|
|
||
| # Change the current directory to the script's directory | ||
| cd "$CURRENT_DIR" || exit | ||
|
|
||
| # Get the current subscription | ||
| SUBSCRIPTION_NAME=$(az account show --query name --output tsv) | ||
|
|
||
| # Check if the resource group already exists | ||
| echo "Checking if [$RESOURCE_GROUP_NAME] resource group actually exists in the [$SUBSCRIPTION_NAME] subscription..." | ||
| az group show --name $RESOURCE_GROUP_NAME &>/dev/null | ||
|
|
||
| if [[ $? != 0 ]]; then | ||
| echo "No [$RESOURCE_GROUP_NAME] resource group actually exists in the [$SUBSCRIPTION_NAME] subscription" | ||
| echo "Creating [$RESOURCE_GROUP_NAME] resource group in the [$SUBSCRIPTION_NAME] subscription..." | ||
|
|
||
| az group create --name $RESOURCE_GROUP_NAME --location "$LOCATION" 1>/dev/null | ||
|
|
||
| if [[ $? == 0 ]]; then | ||
| echo "[$RESOURCE_GROUP_NAME] resource group successfully created in the [$SUBSCRIPTION_NAME] subscription" | ||
| else | ||
| echo "Failed to create [$RESOURCE_GROUP_NAME] resource group in the [$SUBSCRIPTION_NAME] subscription" | ||
| exit 1 | ||
| fi | ||
| else | ||
| echo "[$RESOURCE_GROUP_NAME] resource group already exists in the [$SUBSCRIPTION_NAME] subscription" | ||
| fi | ||
|
|
||
| # Generate the deployment secrets; main.bicepparam reads them from the environment | ||
| export POSTGRES_ADMIN_PASSWORD=$(openssl rand -hex 10) | ||
| export SIGN_KEY=$(openssl rand -hex 16) | ||
| export INTERNAL_TOKEN=$(openssl rand -hex 12) | ||
|
lazarkanelov marked this conversation as resolved.
|
||
|
|
||
| # Validate the Bicep template | ||
| echo "Validating the [$TEMPLATE] Bicep template..." | ||
| az deployment group validate \ | ||
| --resource-group $RESOURCE_GROUP_NAME \ | ||
| --template-file $TEMPLATE \ | ||
| --parameters $PARAMETERS \ | ||
| --output none | ||
|
|
||
| if [[ $? == 0 ]]; then | ||
| echo "[$TEMPLATE] Bicep template successfully validated" | ||
| else | ||
| echo "Failed to validate the [$TEMPLATE] Bicep template" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Deploy the Bicep template | ||
| echo "Deploying the [$TEMPLATE] Bicep template..." | ||
| DEPLOYMENT_OUTPUTS=$(az deployment group create \ | ||
| --name $DEPLOYMENT_NAME \ | ||
| --resource-group $RESOURCE_GROUP_NAME \ | ||
| --template-file $TEMPLATE \ | ||
| --parameters $PARAMETERS \ | ||
| --query properties.outputs) | ||
|
|
||
| if [[ $? == 0 ]]; then | ||
| echo "[$TEMPLATE] Bicep template successfully deployed" | ||
| else | ||
| echo "Failed to deploy the [$TEMPLATE] Bicep template" | ||
| exit 1 | ||
| fi | ||
|
|
||
| WEB_APP_NAME=$(echo "$DEPLOYMENT_OUTPUTS" | jq -r .webAppName.value) | ||
| FUNCTION_APP_NAME=$(echo "$DEPLOYMENT_OUTPUTS" | jq -r .functionAppName.value) | ||
| WEB_APP_HOSTNAME=$(echo "$DEPLOYMENT_OUTPUTS" | jq -r .webAppHostName.value) | ||
| POSTGRES_HOST=$(echo "$DEPLOYMENT_OUTPUTS" | jq -r .postgresHost.value) | ||
| POSTGRES_PORT=$(echo "$DEPLOYMENT_OUTPUTS" | jq -r .postgresPort.value) | ||
|
|
||
| if [[ -z "$WEB_APP_NAME" || -z "$FUNCTION_APP_NAME" ]]; then | ||
| echo "Web App Name or Function App Name is empty. Exiting." | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Persist the PostgreSQL credentials for scripts/validate.sh, matching the | ||
| # contract of scripts/deploy.sh. | ||
| cat >"$CURRENT_DIR/../scripts/.last_deploy.env" <<EOF | ||
| POSTGRES_ADMIN_PASSWORD=$POSTGRES_ADMIN_PASSWORD | ||
| POSTGRES_HOST=$POSTGRES_HOST | ||
| POSTGRES_PORT=$POSTGRES_PORT | ||
| EOF | ||
|
|
||
| # Create the zip package of the web app | ||
| cd "$CURRENT_DIR/../src" || exit | ||
| if [ -f "$WEBAPP_ZIPFILE" ]; then | ||
| rm "$WEBAPP_ZIPFILE" | ||
| fi | ||
| echo "Creating zip package of the web app..." | ||
| zip -r "$WEBAPP_ZIPFILE" app.py gunicorn.conf.py requirements.txt | ||
|
|
||
| # Deploy the web app | ||
| echo "Deploying web app [$WEB_APP_NAME] with zip file [$WEBAPP_ZIPFILE]..." | ||
| az webapp deploy \ | ||
| --resource-group $RESOURCE_GROUP_NAME \ | ||
| --name "$WEB_APP_NAME" \ | ||
| --src-path "$WEBAPP_ZIPFILE" \ | ||
| --type zip \ | ||
| --async true 1>/dev/null | ||
|
|
||
| if [[ $? == 0 ]]; then | ||
| echo "Web app [$WEB_APP_NAME] deployed successfully" | ||
| else | ||
| echo "Failed to deploy web app [$WEB_APP_NAME]" | ||
| exit 1 | ||
| fi | ||
| rm -f "$WEBAPP_ZIPFILE" | ||
|
|
||
| # Create the zip package of the function app | ||
| cd "$CURRENT_DIR/../function" || exit | ||
| if [ -f "$WORKER_ZIPFILE" ]; then | ||
| rm "$WORKER_ZIPFILE" | ||
| fi | ||
| echo "Creating zip package of the function app..." | ||
| zip -r "$WORKER_ZIPFILE" function_app.py host.json requirements.txt | ||
|
|
||
| # Deploy the function app | ||
| echo "Deploying function app [$FUNCTION_APP_NAME] with zip file [$WORKER_ZIPFILE]..." | ||
| az functionapp deploy \ | ||
| --resource-group $RESOURCE_GROUP_NAME \ | ||
| --name "$FUNCTION_APP_NAME" \ | ||
| --src-path "$WORKER_ZIPFILE" \ | ||
| --type zip \ | ||
| --async true 1>/dev/null | ||
|
|
||
| if [[ $? == 0 ]]; then | ||
| echo "Function app [$FUNCTION_APP_NAME] deployed successfully" | ||
| else | ||
| echo "Failed to deploy function app [$FUNCTION_APP_NAME]" | ||
| exit 1 | ||
| fi | ||
| rm -f "$WORKER_ZIPFILE" | ||
|
|
||
| echo "Deployment completed. Web app available at: http://$WEB_APP_HOSTNAME" | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One last comment @lazarkanelov Can you ask Claude to create a diagram for the app? I don't pretend that you create a diagram manually, just ask Claude to create a mermaid diagram, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!