v0.5.1: the update alert could have broken middlewared at startup
middlewared's alert.load() imports every file in alert/source/ with NO try/except:
def load(self):
for module in load_modules(.../alert/source):
for cls in load_classes(module, AlertSource, (ThreadedAlertSource,)):
...
and it runs during setup. A module that raises on import therefore takes
middlewared's startup down with it -- exactly the class of failure this project
exists to avoid.
apply.sh now COMPILES the substituted alert source and refuses to write it if it
does not parse. An uninstalled alert is a missing convenience; a broken one is a
broken box.
@PATCH_DIR@ is also substituted with repr() rather than raw, so a repository path
containing a quote or backslash yields a valid Python literal instead of a syntax
error in the installed module.
The alert source no longer mutates sys.path. It loaded tools/release_notes.py via
sys.path.insert(0, ...), which shadows the stdlib for that interpreter -- and
ThreadedAlertSource runs in middlewared's thread pool, so mutating sys.path is a
race. It now loads by file path with importlib.
New tests guard every import-time failure mode: the module compiles, apply.sh
compiles before writing, awkward paths (quotes, backslashes) still produce valid
modules, nothing but imports/constants/classes runs at module scope, every
AlertClass name ends in "AlertClass" (AlertClassMeta raises NameError otherwise),
the alert text placeholders match the args passed, and no git command that writes
to .git is ever used.
152 tests, ruff and shellcheck -S style clean.
This commit is contained in:
@@ -1,5 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## v0.5.1 — 2026-07-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- **The update alert could have broken middlewared at startup.** middlewared's
|
||||
`alert.load()` imports every file in `alert/source/` with **no try/except**, and
|
||||
it runs during setup — so a module that raises on import takes middlewared down
|
||||
with it. `apply.sh` now **compiles the substituted alert source and refuses to
|
||||
write it** if it does not parse. An uninstalled alert is a missing convenience;
|
||||
a broken one is a broken box.
|
||||
|
||||
- **`@PATCH_DIR@` is substituted with `repr()`**, so a repository path containing
|
||||
a quote or a backslash produces a valid Python literal instead of a syntax error
|
||||
in the installed module.
|
||||
|
||||
- **The alert source no longer mutates `sys.path`.** It loaded
|
||||
`tools/release_notes.py` via `sys.path.insert(0, …)`, which shadows the stdlib
|
||||
for that interpreter — and `ThreadedAlertSource` runs in middlewared's thread
|
||||
pool, so mutating `sys.path` is a race. It now loads the module by file path with
|
||||
`importlib`.
|
||||
|
||||
### Notes
|
||||
|
||||
Timing, for the record: `process_alerts` is `@periodic(60)` and
|
||||
`alert_source_last_run` is in-memory, so the check runs **within 60 seconds of any
|
||||
middlewared restart** (which this patch performs at every boot) and otherwise
|
||||
**within 24 hours** of a release.
|
||||
|
||||
## v0.5.0 — 2026-07-13
|
||||
|
||||
### Added
|
||||
|
||||
Reference in New Issue
Block a user