mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-08-13 08:22:36 +01:00
5ea8a48e1b3cd556f19dfd3a87ac24d61676b7a2
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
96798b5041 | Migrate addon API error metadata to app naming (#7058) | ||
|
|
c119e0cff1 |
Rename Docker app container and builder names from addon_ to app_ (#7024)
* Rename Docker app container and builder names from addon_ to app_ * Update container state event tests to app_ names * Migrate legacy app container names on attach * Fix error handling * Fixes from feedback * Fix pytest --------- Co-authored-by: Stefan Agner <stefan@agner.ch> |
||
|
|
dfe727410c |
Fix length validation of str and password options with bounds (#7044)
* Fix length validation of str and password options with bounds App option schemas like str(1,32) validated the string value with vol.Range, which compares the value itself against the numeric bounds. Comparing a str with a float raises TypeError, which voluptuous reports as "invalid value or type (must have a partial ordering)". As a result, saving options always failed for any add-on using a bounded str or password schema; unbounded str/password was unaffected since vol.Range without bounds performs no comparison. Use vol.Length to check the string length against the bounds, which is the documented meaning of str(min,max). Also replace the str(value) literal (a self-equality check) with the str type so non-string values still fail validation, now with a clearer error message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Update API test for changed non-string option error message Validating a str option against the str type instead of the str(value) literal changed the voluptuous error for non-string values from "not a valid value" to "expected str". Update the expected message in the API options error test accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
35542d5bcc |
Replace addon with app in docker container names for apps (#6997)
* Support dual app/addon log identifiers during migration * Use single journald epoch query for dual app identifiers * Add API-keyed container epoch error handling * Template CONTAINER_LOG_EPOCH via extra fields |
||
|
|
1857753e22 |
Redact app options in info for unprivileged apps (#6953)
The `/addons/{slug}/info` endpoint returned the target app's user options,
which can contain secrets such as passwords and API keys. The security
middleware grants every role (including the default role) access to any
`/.+/info` path, so an installed app with `hassio_api: true` and the default
role could read another app's options simply by requesting its info.
Redact the options field in info_data() unless the caller is entitled to see
it: Home Assistant Core (and other non-app internals), the app reading its
own info, or an app with the manager or admin role. Other apps reading a
different app's info now receive an empty options dict while all non-secret
metadata stays available for discovery. This mirrors the existing self-only
restriction on the dedicated /options/config endpoint.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
a973d22e35 |
Derive App state from container state (#6890)
* Derive App state from container state
The App.state setter mixed two responsibilities: it both mutated a
private `_state` field and dispatched side effects (WebSocket events,
issue dismissal, startup_event signaling). On top of that, an installed
but never-started app stayed in AppState.UNKNOWN forever, because the
attach() image-only fallback never fires a container state-change event
and the AppState therefore kept its constructor default. Conceptually,
ContainerState.UNKNOWN ("container does not exist") and AppState.UNKNOWN
("nothing observed yet") happened to share a name but meant different
things, which made the distinction easy to lose.
Make App.state a pure derived property. The source of truth is the last
observed ContainerState (cached on the App), plus a sticky operation-
error flag for start/stop failures that the docker event stream cannot
reflect. When no container has been observed yet, the derivation falls
back to install signals: an attached instance (image present) is
STOPPED, otherwise UNKNOWN. As a side effect, an installed-but-never-
started app now correctly reports STOPPED instead of UNKNOWN.
container_state_changed updates the cached container state and routes
all side effects through a single _emit_state_change helper that diffs
old vs new derived state. The two start/stop failure paths route
through _set_operation_error. Uninstall resets the cached signals so
the derivation naturally returns UNKNOWN.
Tests use a new tests/common.force_app_state helper that pokes the
underlying signals directly; the production class no longer carries
test-only setters.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix App state drive to AppState.UNKNOWN
* Unify state mutation through _update_state
Previously, state-driving signal changes were spread across two helpers
(_set_operation_error, _emit_state_change(old_state)) and required each
caller to capture self.state before mutating a private field — leaking
implementation details to call sites and raising the "why am I emitting
the old state?" question pointed out in code review.
Replace both helpers with a single _update_state(*, container_state=,
operation_error=) entry point. Callers describe what changed via
keyword arguments (None leaves a signal untouched); the helper captures
the previous state, applies the updates, recomputes the derived state
and emits side effects if anything changed.
Diff against a tracked _last_state instead of a freshly derived
"current" state, so that an out-of-band mutation between updates does
not silently shift the comparison baseline. The concrete case is
App.uninstall: instance.remove() clears the docker meta mid-flow, which
would otherwise reshape the derivation (RUNNING with no healthcheck
becomes STARTED instead of STARTUP) and suppress the STARTUP transition
that resolves the start-wait task. As a side effect, the initial
UNKNOWN -> STOPPED transition on attach is also now reliably emitted.
Switch the uninstall path to ContainerState.UNKNOWN ("we know there is
no container") rather than the constructor sentinel None.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Cache app state instead of deriving on every read
Building on the previous commit, make App.state a plain read of a
cached _state field rather than re-deriving on every property access.
The derivation moves to _derive_state(), and _update_state() is the
sole place that recomputes and assigns _state, so the value consumers
read always matches what was last emitted to listeners.
This removes the _last_state bookkeeping introduced previously: with a
single cached value there is no longer a separate "derived now" vs
"last emitted" distinction to reconcile, and out-of-band mutations
(e.g. instance.remove() clearing _meta during uninstall) can no longer
silently shift what state returns between updates.
Call _update_state() at the end of load() so the cached state settles
once attach() has run. Image-only attaches do not fire a docker event,
so without this an installed app would stay in the constructor-default
UNKNOWN until first start; this also makes the initial transition on
attach observable to listeners.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Pass operation error to _derive_state instead of storing it
The two state-driving signals were not symmetric. _container_state is
genuinely persisted state ("the last thing docker told us") that
re-derivation legitimately reads across calls. _operation_error, on the
other hand, is a momentary "force ERROR for this transition" signal; the
persistence of an error condition already lives in the cached _state.
Storing it as an instance attribute implied a sticky cross-call behavior
that no call path actually exercised: every caller either set it
explicitly right before deriving (start/stop failures, container events)
or ran argless only at load time, where no failure has occurred.
Drop the _operation_error field and pass operation_error as a parameter
to _derive_state(), defaulting to False in _update_state(). A container
observation now supersedes a prior error implicitly via the default,
which lets the container-event and uninstall call sites drop their
explicit operation_error=False.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Settle load state synchronously from current_state
The argless _update_state() settle at the end of load() raced attach()'s
container-state event. attach() fires DOCKER_CONTAINER_STATE_CHANGE via
the bus, which schedules the container_state_changed listener as a task
rather than running it inline. In the deprecated-arch early-return path
there is no await between attach() and the settle, so the listener had
not run yet: _container_state was still None and the settle derived
STOPPED (instance attached) — emitting a transient UNKNOWN->STOPPED even
for a running container before the listener corrected it. The main path
only avoided this incidentally, by having awaits (check_image,
save_persist) in between for the listener to run.
Derive the load-time state synchronously from instance.current_state()
instead of relying on the asynchronously delivered event. current_state()
returns the real container state, or UNKNOWN when only an image is
present (which derives to STOPPED), so both paths settle correctly
without racing the event.
Add a regression test that loading a running container settles to
STARTED, and mock current_state() in the state-listener test which
relies on a clean UNKNOWN baseline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
0f881d69fe |
Model client-state Apps* errors as APIError (#6856)
* apps: model client-state Apps* errors as APIError Follow-up on #6739: with HassioError now logged and captured by Sentry in api_process, a handful of Apps* exceptions raised from AppManager.install/update/rebuild and AppModel._validate_availability surfaced as "unexpected" 400s with a noisy log entry and a Sentry event, even though they are all user/client-state errors (clicked install on an already-installed add-on, "no update available", local and store versions diverged, system architecture/machine/HA version incompatible, etc.). The dominant offender is SUPERVISOR-1JVV ("No update available for app core_mosquitto", ~19k events / ~12k users), but several siblings show the same shape. Map these through properly so the API returns clean, structured 400s: - Add modeled APIError subclasses in exceptions.py for the previously raw raises in apps/manager.py: AppAlreadyInstalledError, AppNotFoundError, AppNotInstalledError, AppNotInStoreError, AppNoUpdateAvailableError, AppRebuildVersionChangedError, AppRebuildImageBasedError. Each gets a stable error_key, a message_template, and an "addon" extra_field. - Add APIError to AppNotSupportedArchitectureError, AppNotSupportedMachineTypeError and AppNotSupportedHomeAssistantVersionError so they behave the same as the other Apps* APIErrors instead of being treated as unexpected. - Pass the app's display name (app.name from the add-on config) instead of the slug to extra_fields wherever an App or AppModel is available at the raise site, so users see "Mosquitto broker" rather than "core_mosquitto" in error messages. Slug is only used as a fallback when no app object exists (install of an unknown slug, update/rebuild of a slug that is not installed). - Update raise sites in apps/manager.py and apps/model.py to use the new typed exceptions and the addon= keyword. These are all runtime states users hit during normal interaction with the apps UI, not Supervisor bugs worth paging on. * apps: include slug alongside name in Apps* APIError extra_fields Address review feedback from #6856: clients still need the slug to look up additional add-on information (the name is for display only), and we should be consistent about it across the Apps* errors touched by this PR. Every Apps* APIError raised with an App/AppModel available now carries both `addon` (display name, used by the message_template) and `slug` in extra_fields. Raise sites in apps/manager.py and apps/model.py pass both. The two errors raised before an app object exists keep slug-only extra_fields and use {slug} in their message: - AppNotFoundError (install of an unknown slug) - AppNotInstalledError (update/rebuild of a slug not in self.local) Pre-existing Apps* APIErrors outside the scope of this PR (AppUnknownError, AppConfigurationInvalidError, AppBootConfigCannot ChangeError, AppNotRunningError, AppPortConflict, AppNotSupportedWrite StdinError, AppBuild*) will be migrated in a follow-up. * apps: introduce AppAPIError base for uniform addon/slug extra_fields Address review follow-up on #6856: the addon/slug convention was enforced only by hand-rolled __init__s, easy to drift on (forget slug, use a different key, etc.). Promote it to a base class that owns the shape of extra_fields for all App-related API errors. - Add AppAPIError(AppsError, APIError). Its __init__ takes `app: AppModel | App | AppStore | str` and uniformly populates extra_fields with `addon` (display name) and `slug`. Pass a string when no app object exists; only `slug` is set in that case. Extra per-error fields flow through **extra_fields and merge with the defaults. - Convert the new exceptions added in this PR (AppAlreadyInstalledError, AppNotFoundError, AppNotInstalledError, AppNotInStoreError, AppNoUpdateAvailableError, AppRebuildVersionChangedError) into thin subclasses that only declare error_key and message_template -- the __init__ is inherited. - Migrate the AppNotSupported* errors (architecture, machine type, HA version) and AppRebuildImageBasedError to use AppAPIError too; their bespoke per-error fields go through **extra_fields. They keep inheriting AppNotSupportedError so `except AppNotSupportedError` callers (e.g., AppModel._available) still work; MRO routes __init__ through AppAPIError. - Update raise sites in apps/manager.py and apps/model.py to pass `app=<obj-or-slug>` instead of repeating `addon=...` and `slug=...`. Pre-existing App* APIErrors outside this PR's scope (AppUnknownError, AppConfigurationInvalidError, AppBootConfigCannotChangeError, AppNotRunningError, AppPortConflict, AppNotSupportedWriteStdinError, AppBuild*) will be migrated to AppAPIError in a follow-up; the base class is in place for them. * apps: tighten AppAPIError model per review Address mdegat01's two follow-ups on #6856 (review approved as-is, this is the cleanup): - AppNotFoundError and AppNotInstalledError are raised before any App/AppModel object exists (unknown slug; not-installed slug). Pull them out of AppAPIError and inherit (AppsError, APIError) directly with a slug-only __init__ + {slug} message_template. Removes the conceptually-wrong str branch from AppAPIError.__init__: it now strictly requires an app-like object with .name and .slug. - Restore per-class typed __init__ on AppNotSupportedArchitectureError, AppNotSupportedMachineTypeError and AppNotSupportedHomeAssistantVersionError so callers get an explicit signature for the bespoke architectures/machine_types/version params instead of dumping them through **extra_fields. Each override just delegates to AppAPIError.__init__, which keeps ownership of the addon/slug shape. The list-joining for architectures/machine_types moves back into the override (raise sites pass the raw list again). AppRebuildImageBasedError takes no bespoke fields and stays a plain AppAPIError subclass. |
||
|
|
f8880a72be |
Rename addon/addons to app/apps in filenames and imports (#6837)
* Rename addon/addons to app/apps in filenames and imports Continues the addon→app terminology migration (#6786). Renames all source files, test files, fixture files, and directories that contained 'addon'/'addons' in their names, and updates all imports accordingly. Resolution check files in supervisor/resolution/checks/ that were renamed override the slug property to preserve the existing API contract (slugs are exposed via the resolution info API and used to run checks by name). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename add-on.json fixture --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |