Skip to content

Give every sample its own name prefix, and exit with an error when a step fails - #148

Merged
paolosalvatori merged 9 commits into
mainfrom
samples-runner-unique-prefix
Oct 29, 2026
Merged

paolosalvatori merged 9 commits into
mainfrom
samples-runner-unique-prefix

Conversation

@paolosalvatori

@paolosalvatori paolosalvatori commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

run-samples.sh runs the samples one after another on one emulator, and after each sample it deletes every resource group with --no-wait and sleeps 2 seconds. 60 of its 81 entries then reuse the previous entry's group (local-rg, local-aca-rg), so they write into a group that may still be being deleted.
Azure refuses such writes with 409 ResourceGroupBeingDeleted, the emulator is about to do the same (SMF-998), and today the emulator accepts them and leaves the new resources behind once the group is gone: 18 Terraform entries already fail on azurerm's existence check in a full local run because of it.
CI is not affected (each job runs one entry on a fresh emulator), but full local runs are.

While checking those entries, it turned out that several scripts can end with status 0 after a failed step, so the runner and CI report success for a sample that did not deploy.

Changes

  • run-samples.sh exports its own PREFIX for every entry: the letter l and four random lowercase letters, never the previous entry's. Every group and resource name then differs from the previous sample's, so nothing waits for a delete and neither CI nor a local run gets slower. The prefix has the length and the characters of the default local, so no Azure name changes length. Running a sample by hand still uses local.
  • The 27 scripts that hardcoded PREFIX='local' now read PREFIX="${PREFIX:-local}", like the other 89.
  • Five deploy scripts passed no prefix to their template, which took local from its parameter file or variable default: url-shortener, eventhubs and eventhubs-eventgrid Bicep, and eventhubs and eventhubs-eventgrid Terraform. They now pass it, on every plan and apply for Terraform, where it goes out only when PREFIX is set so a prefix from terraform.tfvars or TF_VAR_prefix still applies otherwise.
  • With PREFIX set, the 16 call scripts the runner uses without an app name look for the only app in ${PREFIX}-rg instead of the whole subscription, where the previous entry's group can still be being deleted.
  • The URL shortener keeps its deployment state in scripts/.last_deploy.<prefix>.env, one file per prefix.
  • The container-apps-blob-storage and aci-blob-storage Terraform scripts stop before creating anything when the Terraform state of a previous run tracks another resource group, which the apply would otherwise replace. (api-management-function-app Bicep already reads it with readEnvironmentVariable('PREFIX', 'local').)
  • 146 failure branches in 39 scripts ended with a bare exit, which returns the status of the echo before it, 0. They now exit 1.
  • Every new check follows the samples' own pattern: the command, then if [[ $? == 0 ]] with a success message, else a "Failed to ..." message naming the resource in brackets and exit 1; captured values are tested with if [[ -n $VAR ]].
  • web-app-cosmosdb-nosql-api (Python and .NET) checked none of its writes after the resource group, and export VAR=$(az ...) hid any failure of the command. Every az call, the zip and the deploy now stop the script with a message naming the failed step.
  • container-apps-blob-storage (Terraform and Bicep) did not check the registry create, its login server and admin credentials, the image tag or the docker login. The Terraform deploy also ignored the resource group create, terraform init and terraform plan, and swallowed every failure of the group import with || true. Each now stops the script with a message; the import is skipped when the group is already in the state (a re-run), the only failure the || true meant to tolerate.

Tests

  • bash -n on all 68 changed scripts: no errors.
  • shellcheck, before and after, on every changed script: no new finding in a changed line. The extra notes (SC2086, SC2181) are on unchanged lines, because the names now come from PREFIX in the environment rather than a literal, as in the 89 scripts that already read it that way.
  • The new prefix function, 1000 times under the runner's set -euo pipefail: every value matches ^l[a-z]{4}$, no two in a row are equal, and the value reaches child processes.
  • run-samples.sh --list, which the CI uses to build its matrix: identical to main.
  • pre-commit: passed.
  • Not run locally: the samples themselves against the emulator; this PR's CI runs them on Linux and Windows.

Related

Fixes SMF-999
Fixes SMF-1000

🤖 Generated with Claude Code

https://claude.ai/code/session_01FvNuJJpEr2yMWrV5cbJi2Z

paolosalvatori and others added 2 commits October 29, 2026 14:46
run-samples.sh runs the samples one after another on one emulator, and after each sample it deletes every resource group with --no-wait. 60 of its 81 entries then reuse the previous entry's group (local-rg, local-aca-rg), so they write into a group that may still be being deleted: Azure refuses that with 409 ResourceGroupBeingDeleted, the emulator will too (SMF-998), and today the emulator accepts it and leaves the new resources behind once the group is gone (18 Terraform entries already fail on azurerm's existence check because of that).

The runner now exports its own PREFIX for every entry: the letter l and four random lowercase letters, never the previous entry's, so every group and resource name differs from the previous sample's and nothing waits for a delete. The prefix has the length and characters of the default "local", so no name changes length.

Every sample builds its names from PREFIX: the 27 scripts that hardcoded PREFIX='local' now read PREFIX="${PREFIX:-local}" like the other 89, and the five deploy scripts whose template took the prefix from its parameter file or variable default (url-shortener and eventhubs Bicep, eventhubs-eventgrid Bicep, eventhubs and eventhubs-eventgrid Terraform) pass it explicitly. Running a sample by hand still uses local.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvNuJJpEr2yMWrV5cbJi2Z
The samples runner and the samples CI trust each entry's exit status, but several scripts could end with status 0 after a failed step:

- 146 failure branches in 39 scripts ended with a bare exit, which returns the status of the echo before it, 0. They now exit 1.
- web-app-cosmosdb-nosql-api (Python and .NET) checked none of its writes after the resource group, and its two export VAR=$(az ...) lines hid any failure of the command. Every az call, the zip and the deploy now stop the script with a message naming the failed step.
- container-apps-blob-storage (Terraform and Bicep) did not check the registry create, its login server and admin credentials, the image tag or the docker login; the Terraform deploy also ignored the resource group create, terraform init, terraform plan and its outputs, and swallowed every failure of the resource group import with || true. Each now stops the script with a message; the import is skipped when the group is already in the state (a re-run), which is the only failure the || true meant to tolerate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvNuJJpEr2yMWrV5cbJi2Z
@paolosalvatori
paolosalvatori requested a review from a team as a code owner October 29, 2026 13:47
Copilot AI balanced review requested due to automatic review settings October 29, 2026 13:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Prefix handling can trigger destructive Terraform replacements and mix credentials between deployments, while some sample tests can fail or target the wrong resources.

5 open issues
What changed in this PR

This PR improves reliability when running Azure samples sequentially on a shared emulator by varying resource-name prefixes and making deployment failures return nonzero exit codes.

Changes:

  • Generates a five-letter prefix per runner entry and propagates it through deployment, validation, and cleanup scripts.
  • Replaces bare failure exits with exit 1 across sample deployments.
  • Adds explicit failure checks to Cosmos DB NoSQL and Container Apps deployment steps.
File Description
samples/​web-app-sql-database/​python/​scripts/​deploy.sh Returns failure on SQL server creation errors.
samples/​web-app-sql-database/​python/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​web-app-sql-database/​dotnet/​scripts/​deploy.sh Returns failure on SQL server creation errors.
samples/​web-app-sql-database/​dotnet/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​web-app-postgresql-flexible-server/​python/​scripts/​deploy.sh Returns failure on networking setup errors.
samples/​web-app-postgresql-flexible-server/​python/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​web-app-postgresql-flexible-server/​dotnet/​scripts/​deploy.sh Returns failure on networking setup errors.
samples/​web-app-postgresql-flexible-server/​dotnet/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​web-app-mysql-flexible-server/​python/​scripts/​deploy.sh Returns failure on networking setup errors.
samples/​web-app-mysql-flexible-server/​python/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​web-app-mysql-flexible-server/​dotnet/​scripts/​deploy.sh Returns failure on networking setup errors.
samples/​web-app-mysql-flexible-server/​dotnet/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​web-app-managed-identity/​python/​scripts/​user-assigned.sh Returns failure on group and role-assignment errors.
samples/​web-app-managed-identity/​python/​scripts/​system-assigned.sh Returns failure on group and role-assignment errors.
samples/​web-app-managed-identity/​python/​scripts/​cli.sh Returns failure on resource-group creation errors.
samples/​web-app-managed-identity/​python/​scripts/​api.sh Returns failure on resource-group creation errors.
samples/​web-app-managed-identity/​python/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​web-app-managed-identity/​dotnet/​scripts/​user-assigned.sh Returns failure on group and role-assignment errors.
samples/​web-app-managed-identity/​dotnet/​scripts/​system-assigned.sh Returns failure on group and role-assignment errors.
samples/​web-app-managed-identity/​dotnet/​scripts/​cli.sh Returns failure on resource-group creation errors.
samples/​web-app-managed-identity/​dotnet/​scripts/​api.sh Returns failure on resource-group creation errors.
samples/​web-app-managed-identity/​dotnet/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​web-app-custom-image/​python/​scripts/​deploy.sh Returns failure on private-network setup errors.
samples/​web-app-custom-image/​python/​bicep/​deploy.sh Returns failure on group and template-validation errors.
samples/​web-app-custom-image/​dotnet/​scripts/​deploy.sh Returns failure on private-network setup errors.
samples/​web-app-custom-image/​dotnet/​bicep/​deploy.sh Returns failure on group and template-validation errors.
samples/​web-app-cosmosdb-nosql-api/​python/​scripts/​deploy.sh Checks provisioning, configuration, packaging, and deployment failures.
samples/​web-app-cosmosdb-nosql-api/​dotnet/​scripts/​deploy.sh Checks provisioning, configuration, packaging, and deployment failures.
samples/​web-app-cosmosdb-mongodb-api/​python/​scripts/​deploy.sh Returns failure on networking setup errors.
samples/​web-app-cosmosdb-mongodb-api/​python/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​web-app-cosmosdb-mongodb-api/​dotnet/​scripts/​deploy.sh Returns failure on networking setup errors.
samples/​web-app-cosmosdb-mongodb-api/​dotnet/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​url-shortener/​python/​terraform/​deploy.sh Honors the supplied prefix.
samples/​url-shortener/​python/​scripts/​validate.sh Uses the supplied prefix for validation.
samples/​url-shortener/​python/​scripts/​deploy.sh Honors the supplied prefix.
samples/​url-shortener/​python/​scripts/​call-web-app.sh Targets the supplied prefix's web app.
samples/​url-shortener/​python/​bicep/​deploy.sh Passes the prefix to validation and deployment.
samples/​servicebus/​java/​terraform/​deploy.sh Honors the supplied prefix.
samples/​servicebus/​java/​scripts/​validate.sh Uses the supplied prefix for validation.
samples/​servicebus/​java/​scripts/​deploy.sh Honors the supplied prefix.
samples/​servicebus/​java/​bicep/​deploy.sh Honors the prefix and returns failure on errors.
samples/​function-app-storage-http/​dotnet/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​function-app-service-bus/​dotnet/​scripts/​deploy.sh Returns failure on provisioning and permission errors.
samples/​function-app-service-bus/​dotnet/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​function-app-managed-identity/​python/​scripts/​user-managed-identity.sh Returns failure on group and role-assignment errors.
samples/​function-app-managed-identity/​python/​scripts/​system-managed-identity.sh Returns failure on group and role-assignment errors.
samples/​function-app-managed-identity/​python/​bicep/​deploy.sh Returns failure on setup and validation errors.
samples/​eventhubs/​python/​terraform/​deploy.sh Passes the prefix to plans and re-apply.
samples/​eventhubs/​python/​scripts/​validate.sh Uses the supplied prefix for validation.
samples/​eventhubs/​python/​scripts/​deploy.sh Honors the supplied prefix.
samples/​eventhubs/​python/​scripts/​cleanup.sh Uses the supplied prefix for cleanup.
samples/​eventhubs/​python/​bicep/​deploy.sh Passes the prefix to validation and deployment.
samples/​eventhubs-eventgrid/​python/​terraform/​deploy.sh Passes the prefix to plans and re-apply.
samples/​eventhubs-eventgrid/​python/​scripts/​validate.sh Uses the supplied prefix for validation.
samples/​eventhubs-eventgrid/​python/​scripts/​deploy.sh Honors the supplied prefix.
samples/​eventhubs-eventgrid/​python/​scripts/​cleanup.sh Uses the supplied prefix for cleanup.
samples/​eventhubs-eventgrid/​python/​bicep/​deploy.sh Passes the prefix to validation and deployment.
samples/​container-apps-blob-storage/​python/​terraform/​deploy.sh Honors the prefix; checks registry, image, and Terraform operations.
samples/​container-apps-blob-storage/​python/​scripts/​validate.sh Uses the supplied prefix for validation.
samples/​container-apps-blob-storage/​python/​scripts/​deploy.sh Honors the supplied prefix.
samples/​container-apps-blob-storage/​python/​scripts/​cleanup.sh Uses the supplied prefix for cleanup.
samples/​container-apps-blob-storage/​python/​bicep/​deploy.sh Honors the prefix and checks registry and image operations.
samples/​aci-blob-storage/​python/​terraform/​deploy.sh Honors the supplied prefix.
samples/​aci-blob-storage/​python/​scripts/​validate.sh Uses the supplied prefix for validation.
samples/​aci-blob-storage/​python/​scripts/​deploy.sh Honors the supplied prefix.
samples/​aci-blob-storage/​python/​scripts/​cleanup.sh Uses the supplied prefix for cleanup.
samples/​aci-blob-storage/​python/​bicep/​deploy.sh Honors the prefix and returns failure on errors.
run-samples.sh Exports a random prefix different from the previous entry's.

Review effort: Balanced

Comment thread samples/container-apps-blob-storage/python/terraform/deploy.sh Outdated
Comment thread samples/eventhubs/python/terraform/deploy.sh Outdated
Comment thread samples/url-shortener/python/scripts/deploy.sh
Comment thread run-samples.sh
Comment thread run-samples.sh
paolosalvatori and others added 7 commits October 29, 2026 14:55
The checks added to the Cosmos DB NoSQL and Container Apps scripts used a pattern these scripts do not use (if ! cmd). They now follow the samples' own: the command, then if [[ $? == 0 ]] with a success message, else a "Failed to ..." message naming the resource in brackets and exit 1; a captured value is tested with if [[ -n $VAR ]], and a Docker or Terraform step with if [[ $? != 0 ]] as the same file already tests its build and apply. The Terraform outputs are read as before: the apply that produces them is checked, and principal_id is empty by design without a managed identity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvNuJJpEr2yMWrV5cbJi2Z
…script

samples/function-app-storage-http/dotnet/scripts/validate.sh wrote local-rg, local-func-test and localstoragetest out literally, so with the runner's per-sample prefix it checked resources that do not exist (the CI run of this PR failed there). It now builds them from PREFIX and SUFFIX as deploy.sh does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvNuJJpEr2yMWrV5cbJi2Z
…IX is set

The runner gives every sample its own prefix and deletes the groups of the sample before without waiting, so the only web or function app in the subscription can be two for a while. With PREFIX set, the call scripts now count and pick the apps of ${PREFIX}-rg, the group every one of these samples deploys into; without it they look in the whole subscription, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvNuJJpEr2yMWrV5cbJi2Z
…n PREFIX is set

Defaulting PREFIX to local and always passing -var prefix overrode a prefix set in terraform.tfvars or TF_VAR_prefix, so a rerun could replace a customized deployment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvNuJJpEr2yMWrV5cbJi2Z
Deployments under different prefixes shared scripts/.last_deploy.env, so a rerun of one could read another's PostgreSQL password and host. The CLI, Bicep and Terraform deploy scripts and the validation script now use scripts/.last_deploy.<prefix>.env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvNuJJpEr2yMWrV5cbJi2Z
… group

Run under another PREFIX next to the state of a previous run, the apply would replace that run's resource group and delete its resources. The two scripts that pre-create the group for the image push now stop first, naming the group the state tracks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvNuJJpEr2yMWrV5cbJi2Z
The check ran before RESOURCE_GROUP_NAME was assigned, so any state left by a previous run, under the same PREFIX too, stopped the deployment. It now runs right after the names are set, before anything is created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvNuJJpEr2yMWrV5cbJi2Z

@paolosalvatori paolosalvatori left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. Rules applied

  • localstack-pro-azure/CLAUDE.md
  • .claude/rules/azure/common/: coding-style.md, hooks.md, patterns.md, security.md, testing.md
  • .claude/rules/azure/python/: cloud-pipeline.md, coding-style.md, hooks.md, patterns.md, security.md, testing.md
  • .claude/rules/azure/scripts/: coding-style.md, hooks.md, patterns.md, security.md, testing.md. These are path-scoped to localstack-pro-azure/**/*.sh and *.bats, so they do not bind this repository. Its scripts follow their own style: the command, a blank line, then if [[ $? == 0 ]] with a success message, or a failure message and exit 1, and no set -e.
  • No Bicep, Terraform or emulator rule applies: the PR changes no .bicep, .tf or emulator code.

2. Existing comments

Copilot's five findings were all answered and resolved in this round: the Terraform state of another run, the Terraform prefix override, the URL shortener's shared state file, the call scripts' subscription-wide app lookup, and the function-app validation names.

3. Findings

samples/container-apps-blob-storage/python/terraform/deploy.sh, samples/aci-blob-storage/python/terraform/deploy.sh

HIGH | state check before the names | Any existing Terraform state stopped the deployment
Description: The check that stops a deployment whose state tracks another run's resource group ran before RESOURCE_GROUP_NAME was assigned. It compared the state's group with an empty name, so a manual rerun under the same PREFIX stopped too. CI never hit it, because the runner deletes the Terraform state before every entry. Fixed in 3d7c253, which moves the check after the name assignments, before anything is created. Run from the scripts' own first lines with a stubbed terraform, it now goes on for the same group, stops for another run's group, and goes on with no state. The previous version stopped for the same group.
Rules applied: General best practice (a check must read the value it compares after it is set).
Before:

# Stop if the Terraform state of a previous run tracks another resource group ...
STATE_RESOURCE_GROUP_NAME=$(terraform state show ...)
if [[ -n $STATE_RESOURCE_GROUP_NAME && $STATE_RESOURCE_GROUP_NAME != "$RESOURCE_GROUP_NAME" ]]; then ...

# Create resource group and ACR first so we can push the image
RESOURCE_GROUP_NAME="${PREFIX}-aca-rg"

After:

# Create resource group and ACR first so we can push the image
RESOURCE_GROUP_NAME="${PREFIX}-aca-rg"
ACR_NAME="${PREFIX}acaacr${SUFFIX}"

# Stop if the Terraform state of a previous run tracks another resource group ...
STATE_RESOURCE_GROUP_NAME=$(terraform state show ...)
if [[ -n $STATE_RESOURCE_GROUP_NAME && $STATE_RESOURCE_GROUP_NAME != "$RESOURCE_GROUP_NAME" ]]; then ...

4. Clean files

The other 83 scripts and the 4 README files have no material findings. What was checked:

  • bash -n on all 85 changed scripts: no syntax errors.
  • ShellCheck at warning level on every changed line, without SC2181 and SC2086, which are this repository's own style: no warnings.
  • All 183 new exit 1 lines sit in failure branches. The two without an error message are the deliberate stop of the state check.
  • set_sample_name_prefix in run-samples.sh builds l plus four random lowercase letters: five lowercase letters like local, so every name keeps its length and leading letter, and never the previous entry's prefix.
  • The three Bicep scripts name their own resources from PREFIX and pass the same value to the template, so script and template always agree.
  • No file still reads the old scripts/.last_deploy.env.

5. Verdict

Approve. The only HIGH finding is fixed in 3d7c253. Posted as a comment, because GitHub does not accept an approval on your own pull request.

@paolosalvatori

Copy link
Copy Markdown
Contributor Author

Review round summary:

  • Copilot's review, 5 findings: each is answered in its thread and resolved. Fixed in 11e79de (Terraform prefix variable), e1fcb4c (one URL shortener state file per prefix), ad0cdcd (call scripts look in the sample's own resource group) and b128ddd (Terraform state of another run, corrected in 3d7c253); already addressed in 539fe5f (function-app validation names).
  • The review above, 1 finding (the Terraform state check ran before the resource group name was set): fixed in 3d7c253.

This branch was successfully deployed

1 active deployment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants