Rewrite the Front Door sample around a catalog API and test it in CI - #124
Conversation
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.
paolosalvatori
left a comment
There was a problem hiding this comment.
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/*.batstoshell/; the real shell and BATS rules live in.claude/rules/azure/scripts/(its frontmatter targets**/*.shand**/*.bats), so that directory was read in its place.- Every ingested rule file is
paths:-scoped tolocalstack-pro-azure/**, and this PR is in the siblinglocalstack-azure-samplesrepo. 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.mdexists 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/andterraform/rules were not read — the PR touches no*.bicep,*.tfor*.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 createtwo-spelling detection works. azure-cli 2.87 + thecdnextension exposes--conditions/--actions, and the extension auto-populates thetypeNamediscriminator per variant viaset_const, so omitting it from the shorthand is correct. --supported-protocols Http Https—AAZListArg.to_cmd_argsetsnargs='+', 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 createandendpoint purgeexists in the extension — including on the unconditionalSTATUS_ROUTEblock atdeploy.sh:484, which does not go through the syntax switch. --probe-protocol Httpagainst*.azurewebsites.netis fine.az functionapp createdefaultshttps_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.HEADexists inazure/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, sojq '.front_door_headers["x-forwarded-host"]'matches what the function emits./shop/2→/api/catalog/2is consistent with the documented URL rewrite source-pattern / origin-path composition, and/statusbeating/*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-Byis not one of Front Door's reserved response headers, soModifyResponseHeaderis 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:
UrlPathmatch values are not normalised. Real Azure ignores a leading slash in a request-path match value.services/cdn/engine/rules_engine/context.pystrips it from the request path, butconditions.py::_comparecompares the configuredmatchValuesentry verbatim, soBeginsWith /shopnever fires. That divergence is what the README bullet in my first comment had taken for Azure behaviour.X-Cachecarries the access-log vocabulary.services/cdn/engine/forward.py:288sets theX-Cacheresponse header from theCacheStatusenum (HIT/MISS/UNCACHEABLE/…), which is Azure's access-logcacheStatusvocabulary. TheX-Cacheresponse header is a separate documented set —TCP_HIT,TCP_REMOTE_HIT,TCP_MISS,PRIVATE_NOSTORE,CONFIG_NOCACHE. A client matching onTCP_HIT, which is a reasonable thing to do, sees nothing from the emulator — which is precisely whyvalidate.shhas 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.
… 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.
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.