Commit Graph
18 Commits
Author SHA1 Message Date
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>
2026-06-01 19:50:06 +02:00
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>
2026-05-13 20:55:46 +02:00
eb3c388618 Migrate persisted 'addon' field to 'app' in config files (#6786)
* Migrate persisted 'addon' field to 'app' in discovery and services config

Rename the 'addon' key to 'app' in persisted configuration files for
discovery messages (discovery.json), service modules (services.json),
and supervisor config (supervisor.json), as part of the broader
addon->app terminology migration.

Changes:
- Add ATTR_ADDON = "addon" to const.py for V1 API compat/migration
- Add ATTR_ADDONS_CUSTOM_LIST = "addons_custom_list" to const.py for migration
- Change ATTR_APPS_CUSTOM_LIST value from "addons_custom_list" to "apps_custom_list"
- Add _migrate_supervisor_config() schema pre-processor in validate.py to
  transparently load old supervisor.json files using the old key
- Add ATTR_ADDON to services/const.py; change ATTR_APP value to "app"
- Add _migrate_addon_to_app() pre-processors to MQTT, MySQL, and discovery
  schemas to load old config files that used the "addon" key
- Rename Message.addon -> Message.app in Discovery and update all references
- Keep hassio_push/discovery payload using "addon" key for HA compatibility
- GET /services/{service} and GET /discovery: V1 returns "addon" key,
  V2 returns "app" key, via dedicated _v1 handler methods following the
  backups/store pattern, registered with AppVersion guards in
  _register_services() and _register_discovery()
- Broaden FileConfiguration schema type annotation to accept vol.All
  validators in addition to vol.Schema

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add schema migration tests for addon->app config key rename

Test that backwards-compatible migration of old 'addon'/'addons_custom_list'
keys to 'app'/'apps_custom_list' works correctly in all affected schemas,
and that the new keys are accepted without modification.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add an __init__ to discovery tests

* Add app_api_client_with_prefix fixture and update V1/V2 tests

Move the app-level V1/V2 fixture to tests/api/conftest.py as
app_api_client_with_prefix for use across any endpoint that requires
app-level credentials (services_role, app.discovery, etc.).

- Add app_api_client_with_prefix fixture to conftest.py
- Update test_set_service_already_provided and test_del_service_not_provided
  to use app_api_client_with_prefix (covers both v1 and v2)
- Add test_get_service_v1_v2_keys asserting addon/app key per version
- Update test_api_discovery_forbidden, test_api_send_del_discovery,
  test_api_invalid_discovery to use app_api_client_with_prefix
- Split test_discovery_not_found into test_discovery_not_found_get
  (uses api_client_with_prefix, GET requires homeassistant) and
  test_discovery_not_found_delete (uses app_api_client_with_prefix)
- Add test_get_discovery_v1_v2_keys asserting addon/app key per version

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-05 11:18:47 +02:00
bc24fb5449 Refactor API registration to support v1/v2 via shared methods (#6769)
* Refactor API registration to support v1/v2 via shared methods

- Add AppVersion StrEnum (V1, V2) to supervisor/api/const.py
- Replace self.v2_app with self._v2_app and expose a versions property
  (dict[AppVersion, web.Application]) computed dynamically so that test
  fixtures reassigning self.webapp are automatically reflected in V1
- All _register_* methods now accept a required app: web.Application
  parameter; version-specific routes are gated with
  "if app is self.versions[AppVersion.V1/V2]:"
- load() loops over enabled_versions (V1 always, V2 when feature-flagged)
  and calls each registration method once per version, no duplication
- Static resources are registered before webapp.add_subapp() to avoid
  registering into a frozen router
- add_subapp uses self.webapp directly for readability
- Fold _register_v2_apps/_register_v2_backups/_register_v2_store into
  their respective unified methods; remove the now-defunct _register_v2_*
  helpers and the _api_apps/_api_backups/_api_store instance vars
- _register_proxy and _register_ingress updated to accept app; legacy
  /homeassistant/* proxy routes gated behind V1 conditional

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add dual v1/v2 parametrization to API tests

All 163 tests across 17 API modules that register identically on both
v1 and v2 now run against both versions via api_client_with_prefix.

- tests/api/conftest.py: advanced_logs_tester switched to
  api_client_with_prefix so log-endpoint tests are auto-parametrized;
  accepts optional v2_path_prefix kwarg for paths that differ by version
- tests/api/test_{auth,discovery,dns,docker,hardware,host,ingress,
  jobs,mounts,network,os,resolution,security,services,supervisor}.py:
  api_client -> api_client_with_prefix with path prefix unpacking
- supervisor/api/__init__.py: _register_panel() moved outside the
  version loop -- frontend static assets are V1-only
- tests/api/test_panel.py: kept on plain api_client (V1-only)

Tests intentionally kept V1-only:
- auth/discovery: use indirect api_client parametrize for addon context
- homeassistant: all tests call legacy /homeassistant/* paths (V1-only)
- jobs (4 tests): inner @Job-decorated classes register names into a
  module-level set; re-running the same test raises RuntimeError

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Extend dual v1/v2 parametrization to homeassistant and jobs tests

tests/api/conftest.py:
- Add core_api_client_with_root fixture parametrized over three paths:
    v1-core:   /core/...          (canonical v1 path)
    v1-legacy: /homeassistant/... (legacy v1 alias, same handlers)
    v2-core:   /v2/core/...       (canonical v2 path)

tests/api/test_homeassistant.py:
- Switch all 17 api_client tests to core_api_client_with_root so each
  test runs against all three access paths (v1 canonical, v1 legacy
  alias, v2 canonical), exercising every registered route

tests/api/test_jobs.py:
- Promote four inner TestClass definitions to module-level helpers
  (_JobsTreeTestHelper, _JobManualCleanupTestHelper,
  _JobsSortedTestHelper, _JobWithErrorTestHelper) so that @Job name
  registration into the global _JOB_NAMES set only happens once at
  import time rather than on each parametrized test run
- Replace closure references to outer-scope coresys with self.coresys
- Use api_client_with_prefix for dual-version coverage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix typo

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-27 23:39:47 +02:00
7fb621234e Add Unix socket support for Core communication with feature flag (#6742)
* Use Unix socket for Supervisor to Core communication

Reintroduce Unix socket support for Supervisor-to-Core communication
(reverted in #6735) with the addition of a feature flag gate. The
feature is now controlled by the `core_unix_socket` feature flag and
disabled by default.

When enabled and Core version supports it, Supervisor communicates with
Core via a Unix socket at /run/os/core.sock instead of TCP. This
eliminates the need for access token authentication on the socket path,
as Core authenticates the peer by the socket connection itself.

Key changes:
- Add FeatureFlag.CORE_UNIX_SOCKET to gate the feature
- HomeAssistantAPI: transport-aware session/url/websocket management
- WSClient: separate connect() (Unix, no auth) and connect_with_auth()
  (TCP) class methods with proper error handling
- APIProxy delegates websocket setup to api.connect_websocket()
- Container state tracking for Unix session lifecycle
- CI builder mounts /run/supervisor for integration tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Sort feature flags alphabetically

* Drop per-call max_msg_size from WSClient

Hardcode the WebSocket message size cap to 64 MB in WSClient and remove
the parameter from WSClient.connect, connect_with_auth, _ws_connect,
and HomeAssistantAPI.connect_websocket. This was only ever overridden
by APIProxy, so threading it through four layers was unnecessary.

max_msg_size is a cap, not a pre-allocation; aiohttp only grows buffers
to the size of actual incoming messages. Supervisor's own control
channel never approaches 64 MB, so unifying the limit has no runtime
cost.

Addresses review feedback on #6742.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 15:03:05 +02:00
ba8c49935b Refactor internal addon references to app/apps (#6717)
* Rename addon→app in docstrings and comments

Updates all docstrings and inline comments across supervisor/ and
tests/ to use the new app/apps terminology. No runtime behaviour
is changed by this commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename addon→app in code (variables, args, class names, functions)

Renames all internal Python identifiers from addon/addons to app/apps:
- Variable and argument names
- Function and method names
- Class names (Addon→App, AddonManager→AppManager, DockerAddon→DockerApp,
  all exception, check, and fixup classes, etc.)
- String literals used as Python identifiers (pytest fixtures,
  parametrize param names, patch.object attribute strings,
  URL route match_info keys)

External API contracts are preserved: JSON keys, error codes,
discovery protocol fields, TypedDict/attr.s field names.
Import module paths (supervisor/addons/) are also unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix partial backup/restore API to remap addons key to apps

The external API accepts `addons` as the request body key (since
ATTR_APPS = "addons"), but do_backup_partial and do_restore_partial
now take an `apps` parameter after the rename. The **body expansion
in both endpoints would pass `addons=...` causing a TypeError.

Remap the key before expansion in both backup_partial and
restore_partial:

    if ATTR_APPS in body:
        body["apps"] = body.pop(ATTR_APPS)

Also adds test_restore_partial_with_addons_key to verify the restore
path correctly receives apps= when addons is passed in the request
body. This path had no existing test coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix merge error

* Adjust AppLoggerAdapter to use app_name

Co-authored-by: Stefan Agner <stefan@agner.ch>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
2026-04-14 16:47:20 +02:00
Stefan AgnerandGitHub 5c5428fde3 Revert "Use Unix socket for Supervisor to Core communication (#6590)" (#6735)
This reverts commit 28fa0b35bd.
2026-04-14 12:28:02 +02:00
28fa0b35bd Use Unix socket for Supervisor to Core communication (#6590)
* Use Unix socket for Supervisor to Core communication

Switch internal Supervisor-to-Core HTTP and WebSocket communication
from TCP (port 8123) to a Unix domain socket.

The existing /run/supervisor directory on the host (already mounted
at /run/os inside the Supervisor container) is bind-mounted into the
Core container at /run/supervisor. Core receives the socket path via
the SUPERVISOR_CORE_API_SOCKET environment variable, creates the
socket there, and Supervisor connects to it via aiohttp.UnixConnector
at /run/os/core.sock.

Since the Unix socket is only reachable by processes on the same host,
requests arriving over it are implicitly trusted and authenticated as
the existing Supervisor system user. This removes the token round-trip
where Supervisor had to obtain and send Bearer tokens on every Core
API call. WebSocket connections are likewise authenticated implicitly,
skipping the auth_required/auth handshake.

Key design decisions:
- Version-gated by CORE_UNIX_SOCKET_MIN_VERSION so older Core
  versions transparently continue using TCP with token auth
- LANDINGPAGE is explicitly excluded (not a CalVer version)
- Hard-fails with a clear error if the socket file is unexpectedly
  missing when Unix socket communication is expected
- WSClient.connect() for Unix socket (no auth) and
  WSClient.connect_with_auth() for TCP (token auth) separate the
  two connection modes cleanly
- Token refresh always uses the TCP websession since it is inherently
  a TCP/Bearer-auth operation
- Logs which transport (Unix socket vs TCP) is being used on first
  request

Closes #6626
Related Core PR: home-assistant/core#163907

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Close WebSocket on handshake failure and validate auth_required

Ensure the underlying WebSocket connection is closed before raising
when the handshake produces an unexpected message. Also validate that
the first TCP message is auth_required before sending credentials.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix pylint protected-access warnings in tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Check running container env before using Unix socket

Split use_unix_socket into two properties to handle the Supervisor
upgrade transition where Core is still running with a container
started by the old Supervisor (without SUPERVISOR_CORE_API_SOCKET):

- supports_unix_socket: version check only, used when creating the
  Core container to decide whether to set the env var
- use_unix_socket: version check + running container env check, used
  for communication decisions

This ensures TCP fallback during the upgrade transition while still
hard-failing if the socket is missing after Supervisor configured
Core to use it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Improve Core API communication logging and error handling

- Remove transport log from make_request that logged before Core
  container was attached, causing misleading connection logs
- Log "Connected to Core via ..." once on first successful API response
  in get_api_state, when the transport is actually known
- Remove explicit socket existence check from session property, let
  aiohttp UnixConnector produce natural connection errors during
  Core startup (same as TCP connection refused)
- Add validation in get_core_state matching get_config pattern
- Restore make_request docstring

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Guard Core API requests with container running check

Add is_running() check to make_request and connect_websocket so no
HTTP or WebSocket connection is attempted when the Core container is
not running. This avoids misleading connection attempts during
Supervisor startup before Core is ready.

Also make use_unix_socket raise if container metadata is not available
instead of silently falling back to TCP. This is a defensive check
since is_running() guards should prevent reaching this state.

Add attached property to DockerInterface to expose whether container
metadata has been loaded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Reset Core API connection state on container stop

Listen for Core container STOPPED/FAILED events to reset the
connection state: clear the _core_connected flag so the transport
is logged again on next successful connection, and close any stale
Unix socket session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Only mount /run/supervisor if we use it

* Fix pytest errors

* Remove redundant is_running check from ingress panel update

The is_running() guard in update_hass_panel is now redundant since
make_request checks is_running() internally. Also mock is_running
in the websession test fixture since tests using it need make_request
to proceed past the container running check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Bind mount /run/supervisor to Supervisor /run/os

Home Assistant OS (as well as the Supervised run scripts) bind mount
/run/supervisor to /run/os in Supervisor. Since we reuse this location
for the communication socket between Supervisor and Core, we need to
also bind mount /run/supervisor to Supervisor /run/os in CI.

* Wrap WebSocket handshake errors in HomeAssistantAPIError

Unexpected exceptions during the WebSocket handshake (KeyError,
ValueError, TypeError from malformed messages) are now wrapped in
HomeAssistantAPIError inside WSClient.connect/connect_with_auth.
This means callers only need to catch HomeAssistantAPIError.

Remove the now-unnecessary except (RuntimeError, ValueError,
TypeError) from proxy _websocket_client and add a proper error
message to the APIError per review feedback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Narrow WebSocket handshake exception handling

Replace broad `except Exception` with specific exception types that
can actually occur during the WebSocket handshake: KeyError (missing
dict keys), ValueError (bad JSON), TypeError (non-text WS message),
aiohttp.ClientError (connection errors), and TimeoutError. This
avoids silently wrapping programming errors into HomeAssistantAPIError.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Remove unused create_mountpoint from MountBindOptions

The field was added but never used. The /run/supervisor host path
is guaranteed to exist since HAOS creates it for the Supervisor
container mount, so auto-creating the mountpoint is unnecessary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Clear stale access token before raising on final retry

Move token clear before the attempt check in connect_websocket so
the stale token is always discarded, even when raising on the final
attempt. Without this, the next call would reuse the cached bad token
via _ensure_access_token's fast path, wasting a round-trip.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add tests for Unix socket communication and Core API

Add tests for the new Unix socket communication path and improve
existing test coverage:

- Version-based supports_unix_socket and env-based use_unix_socket
- api_url/ws_url transport selection
- Connection lifecycle: connected log after restart, ignoring
  unrelated container events
- get_api_state/check_api_state parameterized across versions,
  responses, and error cases
- make_request is_running guard and TCP flow with real token fetch
- connect_websocket for both Unix and TCP (with token verification)
- WSClient.connect/connect_with_auth handshake success, errors,
  cleanup on failure, and close with pending futures

Consolidate existing tests into parameterized form and drop synthetic
tests that covered very little.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:09:38 +02:00
941f7cd2be Change addons to apps in all user-facing strings (#6696)
* Change addons to apps in all user-facing strings

* Fix grammar in errors

* Apply suggestions from code review

Co-authored-by: Jan Čermák <sairon@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Stefan Agner <stefan@agner.ch>

---------

Co-authored-by: Jan Čermák <sairon@users.noreply.github.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
2026-04-07 18:54:40 +02:00
66228f976d Use session.request() instead of getattr dispatch in HomeAssistantAPI (#6541)
Replace the dynamic `getattr(self.sys_websession, method)(...)` pattern
with the explicit `self.sys_websession.request(method, ...)` call. This
is type-safe and avoids runtime failures from typos in method names.

Also wrap the timeout parameter in `aiohttp.ClientTimeout` for
consistency with the typed `request()` signature.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 09:43:55 +01:00
85f8107b60 Recreate aiohttp ClientSession after DNS plug-in load (#5862)
* Recreate aiohttp ClientSession after DNS plug-in load

Create a temporary ClientSession early in case we need to load version
information from the internet. This doesn't use the final DNS setup
and hence might fail to load in certain situations since we don't have
the fallback mechanims in place yet. But if the DNS container image
is present, we'll continue the setup and load the DNS plug-in. We then
can recreate the ClientSession such that it uses the DNS plug-in.

This works around an issue with aiodns, which today doesn't reload
`resolv.conf` automatically when it changes. This lead to Supervisor
using the initial `resolv.conf` as created by Docker. It meant that
we did not use the DNS plug-in (and its fallback capabilities) in
Supervisor. Also it meant that changes to the DNS setup at runtime
did not propagate to the aiohttp ClientSession (as observed in #5332).

* Mock aiohttp.ClientSession for all tests

Currently in several places pytest actually uses the aiohttp
ClientSession and reaches out to the internet. This is not ideal
for unit tests and should be avoided.

This creates several new fixtures to aid this effort: The `websession`
fixture simply returns a mocked aiohttp.ClientSession, which can be
used whenever a function is tested which needs the global websession.

A separate new fixture to mock the connectivity check named
`supervisor_internet` since this is often used through the Job
decorator which require INTERNET_SYSTEM.

And the `mock_update_data` uses the already existing update json
test data from the fixture directory instead of loading the data
from the internet.

* Log ClientSession nameserver information

When recreating the aiohttp ClientSession, log information what
nameservers exactly are going to be used.

* Refuse ClientSession initialization when API is available

Previous attempts to reinitialize the ClientSession have shown
use of the ClientSession after it was closed due to API requets
being handled in parallel to the reinitialization (see #5851).
Make sure this is not possible by refusing to reinitialize the
ClientSession when the API is available.

* Fix pytests

Also sure we don't create aiohttp ClientSession objects unnecessarily.

* Apply suggestions from code review

Co-authored-by: Jan Čermák <sairon@users.noreply.github.com>

---------

Co-authored-by: Jan Čermák <sairon@users.noreply.github.com>
2025-05-06 16:23:40 +02:00
Mike DegatanoandGitHub 31193abb7b FileConfiguration uses executor for I/O (#5652)
* FileConfiguration uses executor for I/O

* Fix credentials tests

* Remove migrate_system_env as its very deprecated
2025-02-26 19:11:11 +01:00
Mike DegatanoandGitHub d8101ddba8 Use status 404 in more places when appropriate (#5480) 2024-12-17 11:18:32 +01:00
Stefan AgnerandGitHub f6faa18409 Bump pre-commit ruff to 0.5.7 and reformat (#5242)
It seems that the codebase is not formatted with the latest ruff
version. This PR reformats the codebase with ruff 0.5.7.
2024-08-13 20:53:56 +02:00
Mike DegatanoandGitHub 74a5899626 Remove discovery config validation from supervisor (#4937)
* Remove discovery config validation from supervisor

* Remove invalid test

* Change validation to require a dictionary for compatibility
2024-03-05 16:25:15 +01:00
Mike DegatanoandGitHub 2c7b417e25 APIForbidden should result in 403 status (#4943) 2024-03-04 11:09:17 -05:00
e60af93e2b List discovery only includes healthy addons (#4451)
Co-authored-by: Stefan Agner <stefan@agner.ch>
2023-07-22 23:27:42 +02:00
Mike DegatanoandGitHub be4a6a1564 Allow discovery messages for unknown services with a warning (#4449)
* Allow discovery messages for unknown services with a warning

* Log at warning level and skip sentry report
2023-07-21 15:05:51 -04:00