Skip to content

Rewrite the Front Door sample around a catalog API and test it in CI - #124

Merged
DrisDary merged 2 commits into
mainfrom
feat/front-door-sample
Sep 22, 2026
Merged

DrisDary merged 2 commits into
mainfrom
feat/front-door-sample

Conversation

@DrisDary

Copy link
Copy Markdown
Contributor

The sample deployed five endpoints across five scenarios and was the only sample CI never tested, which hid three things that stopped it working against the emulator: it printed the wrong development alias, passed the Function App's host:port straight to --host-name, and forced an HTTPS redirect the emulator does not serve.

Two Function Apps now serve a small catalog API behind one endpoint with two routes, an origin group ordered by priority and a rule set that stamps a response header, rewrites /shop to /catalog and redirects a retired path at the edge. The scripts follow the convention of the other samples (deploy.sh, validate.sh, call-front-door.sh, cleanup.sh), and run-samples.sh now runs the validation: routing, route specificity, origin priority, the three rules, caching with purge, the headers the edge adds to the origin request, an origin error passing through, and the endpoint's enabled state.

deploy.sh supports both spellings of az afd rule create: the flattened arguments of Azure CLI 2.83 and earlier, and the --conditions/--actions shorthand of the cdn extension used from 2.85 on.

run-samples.sh also watches each sample's function/ folder, so a change to a sample's application code re-runs it in changed mode.

The sample deployed five endpoints across five scenarios and was the only sample CI
never tested, which hid three things that stopped it working against the emulator: it
printed the wrong development alias, passed the Function App's host:port straight to
--host-name, and forced an HTTPS redirect the emulator does not serve.

Two Function Apps now serve a small catalog API behind one endpoint with two routes, an
origin group ordered by priority and a rule set that stamps a response header, rewrites
/shop to /catalog and redirects a retired path at the edge. The scripts follow the
convention of the other samples (deploy.sh, validate.sh, call-front-door.sh,
cleanup.sh), and run-samples.sh now runs the validation: routing, route specificity,
origin priority, the three rules, caching with purge, the headers the edge adds to the
origin request, an origin error passing through, and the endpoint's enabled state.

deploy.sh supports both spellings of `az afd rule create`: the flattened arguments of
Azure CLI 2.83 and earlier, and the --conditions/--actions shorthand of the cdn
extension used from 2.85 on.

run-samples.sh also watches each sample's function/ folder, so a change to a sample's
application code re-runs it in changed mode.
@DrisDary DrisDary self-assigned this Sep 18, 2026

@paolosalvatori paolosalvatori 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.

Review — Front Door sample rewrite

Thanks for this — the rewrite is a clear improvement. The sample is now actually covered by CI, the three defects called out in the description are genuinely fixed, and I could not find a functional break: every Azure CLI and Front Door claim I checked held up against the REST spec, Microsoft Learn and the installed az 2.87 + cdn extension. Six findings inline: one wrong Azure fact in the README, two test assertions that read stronger than they are, and three small ones.


1. Rules applied

From localstack/localstack-pro at origin/main (verified current — no ingested rule file differs from origin/main):

  • 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

Notes on ingestion:

  • .claude/rules/azure/shell/ does not exist. My runbook maps *.sh/*.bats to shell/; the real shell and BATS rules live in .claude/rules/azure/scripts/ (its frontmatter targets **/*.sh and **/*.bats), so that directory was read in its place.
  • Every ingested rule file is paths:-scoped to localstack-pro-azure/**, and this PR is in the sibling localstack-azure-samples repo. Rules are therefore cited as principles applied, not literal path-scoped violations. Where this repo has an established contrary convention, the repo convention wins.
  • No azure-front-door.md exists under .claude/rules/azure/emulator/ (that directory holds App Configuration, MySQL, Event Hubs, AKS, Service Bus and SQL only), so no service-specific emulator rule applied.
  • bicep/ and terraform/ rules were not read — the PR touches no *.bicep, *.tf or *.tfvars.

Conventions I checked before raising style findings and deliberately did not report: #!/bin/bash rather than #!/usr/bin/env bash (all 135 sample scripts in this repo use it), the absence of set -euo pipefail (only 4 of 135 use strict mode, and the $?-checking style here is incompatible with set -e), and the hardcoded :4566 plus the shared local-rg (both repo-wide sample conventions, and CI shards each sample separately).

2. Existing comments

None found — no reviews, no inline review comments, no issue comments, from humans or bots. Nothing to avoid duplicating.

3. Findings

Six inline comments: 0 HIGH · 3 MEDIUM · 3 LOW. Each carries a one-click suggestion.

4. Clean files

README.md (root) · run-samples.sh (the added "$path/function" watch folder is right for this sample's path, and non-existent folders simply never match) · function/function_app.py · function/host.json (dropping routePrefix: "" restores the default /api prefix, which is exactly what ORIGIN_PATH, PROBE_PATH and the /api/... assertions depend on; [4.*, 5.0.0) is the documented v4 bundle range) · function/requirements.txt · scripts/README.md · scripts/cleanup.sh · scripts/call-front-door.sh (apart from the /tmp paths noted inline) · all deletions — .last_deploy_all.env and the stale requirements.txt.md5 build-cache checksum were committed state that should not have been tracked, and dropping them is a net win.

Verified against Azure and the installed tooling — no finding (9 items)

Each of these was a plausible break that turned out to be correct. Recording them so nobody re-derives them:

  • The az afd rule create two-spelling detection works. azure-cli 2.87 + the cdn extension exposes --conditions/--actions, and the extension auto-populates the typeName discriminator per variant via set_const, so omitting it from the shorthand is correct.
  • --supported-protocols Http Https — AAZListArg.to_cmd_arg sets nargs='+', so the space-separated form is accepted alongside the [Http,Https] shorthand the help shows.
  • Every flag used on az afd route create, origin create, origin-group create and endpoint purge exists in the extension — including on the unconditional STATUS_ROUTE block at deploy.sh:484, which does not go through the syntax switch.
  • --probe-protocol Http against *.azurewebsites.net is fine. az functionapp create defaults https_only=False (command_modules/appservice/custom.py:8443), so the probe gets a 200 rather than a redirect. This matters because only 200 OK counts as healthy and a whole-group probe failure makes Front Door round-robin across all origins — which would have silently broken the priority check.
  • methods=["GET", "HEAD"] — HttpMethod.HEAD exists in azure/functions/decorators/http.py, and HEAD is a supported probe method.
  • whoami's JSON keys are lowercase. azure.functions.BaseHeaders.__init__ lowercases every key and __iter__ yields those, so jq '.front_door_headers["x-forwarded-host"]' matches what the function emits.
  • /shop/2 → /api/catalog/2 is consistent with the documented URL rewrite source-pattern / origin-path composition, and /status beating /* matches the route matching exact-before-wildcard rule.
  • --content-paths '/catalog/*' not raised. The Standard/Premium "Supported path formats" list names only single-path and root purge, but the CLI example on that same page uses --content-paths '/scripts/*'. The docs contradict themselves and the sample follows Microsoft's own CLI example.
  • X-Served-By is not one of Front Door's reserved response headers, so ModifyResponseHeader is allowed to set it.

bash -n passes on all four new scripts and python -m py_compile passes on function_app.py. shellcheck is not installed on my machine, so the static pass the rules ask for was not run. CI is green, including Test: function-app-front-door/python/scripts on both amd64 and arm64.

Out of scope — two emulator parity gaps this sample is written around

Both are in localstack-pro, not in this PR, and neither blocks it. Worth filing, because the README and validate.sh are both shaped by them:

  1. UrlPath match values are not normalised. Real Azure ignores a leading slash in a request-path match value. services/cdn/engine/rules_engine/context.py strips it from the request path, but conditions.py::_compare compares the configured matchValues entry verbatim, so BeginsWith /shop never fires. That divergence is what the README bullet in my first comment had taken for Azure behaviour.
  2. X-Cache carries the access-log vocabulary. services/cdn/engine/forward.py:288 sets the X-Cache response header from the CacheStatus enum (HIT/MISS/UNCACHEABLE/…), which is Azure's access-log cacheStatus vocabulary. The X-Cache response header is a separate documented set — TCP_HIT, TCP_REMOTE_HIT, TCP_MISS, PRIVATE_NOSTORE, CONFIG_NOCACHE. A client matching on TCP_HIT, which is a reasonable thing to do, sees nothing from the emulator — which is precisely why validate.sh has to grep for the substring.

5. Verdict

Approve. Nothing here is a bug, parity break or security issue; the three MEDIUM findings are one wrong Azure fact in the README and two test assertions that are weaker than they read.

Comment thread samples/function-app-front-door/python/README.md Outdated
Comment thread samples/function-app-front-door/python/scripts/validate.sh
Comment thread samples/function-app-front-door/python/scripts/validate.sh Outdated
Comment thread samples/function-app-front-door/python/scripts/validate.sh Outdated
Comment thread samples/function-app-front-door/python/scripts/deploy.sh Outdated
Comment thread samples/function-app-front-door/python/README.md Outdated
… small fixes

The README said a leading slash in a UrlPath match value is silently dead. That is the
emulator's behaviour, not Azure's: the match-conditions page says a leading slash in the
value is ignored, so /shop matches there. The bullet now says what each does and why the
sample writes the value without the slash.

The origin-priority check counted only the standby's answers, so ten failed requests read
as success; it now requires ten answers from the priority-1 origin and reports both
counts. The cache-hit assertion retries like the other propagation-sensitive checks,
because each edge site manages its own cache, and the Azure propagation budget goes to
120 attempts, which is the ten minutes Microsoft budgets for a purge at the five-second
sleep those loops use.

Both scripts take their scratch files from mktemp with an EXIT trap, the origin group
that serves /status is named for what it holds rather than for the standby origin it does
not, and the rule-set bullet names the RequestMethod condition that limits stampHeader to
GET.

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