[webserver] follow symlinks when serving static files#33853
Conversation
Build environments such as Bazel materialize the bundled `webapp/build/` tree by symlinking each file into a sandbox whose targets live outside the served directory. Starlette's `StaticFiles` rejects such symlinks by default (since dagster-io#21048 switched from per-file `Route(FileResponse(...))` to a directory `Mount(StaticFiles(...))`), so dagster-webserver serves 404s for every static asset under Bazel and the UI loads blank. Pass `follow_symlink=True` so symlinked files are served normally. The served directory itself is curated by the build system, so there is no new exposure surface beyond what the previous `FileResponse`-per-file implementation already accepted. Add a regression test that mounts a tmp `webapp/build/` containing a symlinked file whose target lives outside the served tree, mirroring the Bazel sandbox layout from the issue. Fixes dagster-io#33851
Greptile SummaryThis PR adds
Confidence Score: 5/5Safe to merge — the change is a single-parameter addition to an internal static-file mount; the test directly validates the fix against the reported failure mode. The production change is one line with no new logic paths; the only behavior difference is that symlinked files inside No files require special attention.
|
| Filename | Overview |
|---|---|
| python_modules/dagster-webserver/dagster_webserver/webserver.py | Adds follow_symlink=True to StaticFiles inside _static_dir; comment references the tracking issue. Change is minimal and correct. |
| python_modules/dagster-webserver/dagster_webserver_tests/webserver/test_app.py | Adds test_static_resources_follow_symlinks regression test. Structure mirrors the Bazel sandbox layout and follows the same fixture/instance patterns established in conftest.py. |
Sequence Diagram
sequenceDiagram
participant Bazel as Bazel Sandbox
participant BuildDir as webapp/build/static/
participant Symlink as main.css (symlink)
participant Target as ../outside/main.css (real file)
participant StaticFiles as Starlette StaticFiles
participant Client as HTTP Client
Bazel->>BuildDir: create directory structure
Bazel->>Symlink: os.symlink(target outside build/)
Symlink-->>Target: points to real file outside served dir
Client->>StaticFiles: GET /static/main.css
StaticFiles->>Symlink: resolve path
Note over StaticFiles,Symlink: follow_symlink=True allows this
Symlink->>Target: follow symlink
Target-->>StaticFiles: file contents
StaticFiles-->>Client: 200 OK + file contents
Reviews (1): Last reviewed commit: "[webserver] follow symlinks when serving..." | Re-trigger Greptile
|
Hi @SpencerWhitehead7, @smackesey, @alangenfeld — pinging you here since fork PRs can't formally request reviewers from outside the fork. Picked you specifically because:
Happy to expand the test or split the change if any of you would prefer a different approach. No rush — just flagging since the issue (#33851) has zero comments and I wasn't sure who watches this area. |
Summary
Build environments such as Bazel materialize the bundled
webapp/build/tree by symlinking each individual file into a sandbox whose targets live outside the served directory. Starlette'sStaticFilesrejects such symlinks by default — so dagster-webserver returns404 Not Foundfor every static asset under Bazel and the UI loads as a blank page.This regressed when
build_static_routesmoved from per-fileRoute(FileResponse(...))(which transparently followed symlinks) to a directoryMount(StaticFiles(...))in #21048.Pass
follow_symlink=Trueso the directory mount serves symlinked files normally. The served directory is curated by the build system, so this does not expand the exposure surface beyond what the previousFileResponse-per-file implementation already accepted.Changes
dagster_webserver/webserver.py: addfollow_symlink=Trueto theStaticFiles(...)call inside_static_dir. Inline comment links to the issue.dagster_webserver_tests/webserver/test_app.py: addtest_static_resources_follow_symlinksregression test that creates a tmpwebapp/build/with a symlinked CSS file whose target lives outside the served tree (mirroring Bazel's sandbox layout) and asserts the webserver serves it with status 200 and the correct body.Test plan
404 Not Foundfor the symlinked file).test_static_resourcescontinues to pass.ruff check/ruff formatclean.Fixes #33851