Commit Graph
7 Commits
Author SHA1 Message Date
f49b81840e Add app-based mapping options for app configs (#6992)
* Rename addon map options to app equivalents

* Add tests for apps/addons default mount targets

* Fixes from feedback

* Fix tests and clean up validation logic a bit

* Update supervisor/apps/validate.py

* Apply suggestions from code review

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

---------

Co-authored-by: Stefan Agner <stefan@agner.ch>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-08 12:54:34 +02:00
5bc795d2f2 Migrate simple attrs classes to stdlib dataclasses (#7005)
* Migrate simple attrs classes to stdlib dataclasses

Several plain data-holder classes still used the attrs library while
newer code in the project uses stdlib dataclasses. Convert
EventListener, Issue, Suggestion, HealthChanged, SupportedChanged,
Device, Message, HostEntry, ServiceInfo, WhoamiData and the scheduler
_Task to @dataclass, and switch the attr.evolve/attr.asdict call sites
to dataclasses.replace/asdict.

Field semantics are preserved: eq=False maps to compare=False,
hash=False and default factories map directly, and the frozen/slots
flags are kept, so equality, hashing and copy behavior are unchanged.
The jobs module keeps using attrs for its validators and setter hooks,
which have no stdlib dataclass equivalent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add slots to remaining dataclasses, freeze HostEntry

Address review feedback: ServiceInfo, Message and HostEntry kept the
flags of their attrs originals, which did not use slots. Enable slots
on all three. HostEntry instances are never mutated after creation, so
it can also be frozen. Message cannot be frozen because Discovery.send
updates the config field of an existing message when an app re-sends
a discovery message with changed configuration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 18:38:31 +02:00
Mike DegatanoandGitHub 5c65da8a19 Refine Docker timeout handling and add regression tests (#6970)
* Refine Docker timeout handling and add regression tests

* Add network exception path tests for Docker manager

* Add timeout-path tests for Docker app and network

* Remove unnecessary timeout=None overrides from pull calls and tests
2026-06-26 15:15:10 +02:00
dc6a77507b fix(docker): restore add-on device access after USB re-enumeration (#6877)
* fix(docker): register hw listener and match by-id paths for options-based devices

Two bugs caused a crash loop when a USB device re-enumerates to a different
minor number (e.g. ttyACM0→ttyACM1) after a HAOS reboot:

1. _hw_listener was only registered when addon.static_devices was non-empty.
   Addons that expose a device via the options schema (e.g. Z-Wave JS `device:`
   option) never had the listener registered, so add_devices_allowed was never
   called when the device reappeared at a new minor.

2. _hardware_events matched only device.path and device.sysfs against
   static_devices.  When static_devices (or the new options path) contains a
   by-id symlink, the match always failed because by-id paths live in
   device.links.

Fix: extend the listener registration condition to also cover addon.devices
(options-based), and expand the path-matching set to include device.links so
by-id paths resolve correctly.  For options-based devices, compare the incoming
Device against addon.devices (which re-evaluates options.json against the live
hardware list, picking up the new minor number automatically).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(docker): use option_device_paths for cheap by-id hw event matching

Refactor _hardware_events to avoid per-event full options validation
(including pwnd hashing). Introduce AppOptions.extract_device_paths and
AppModel.option_device_paths to extract raw device paths from options
without resolving against live hardware. Use set-intersection against
{device.path, device.sysfs, *device.links} so by-id symlinks match
correctly after re-enumeration for both static and options-based devices.

Update test to use real schema/options setup and simulate a minor-number
change (ttyACM0→ttyACM1) with a stable by-id symlink.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: improve hw listener test coverage and add policy check

Address PR review feedback:
- Add hardware policy check in _hardware_events to prevent bypassing access
  restrictions on hotplug events (follows same pattern as startup cgroup setup)
- Fix test_app_options_device_hw_listener to properly simulate USB re-enumeration
  with different minor numbers (166:0 → 166:1)
- Add test_app_options_device_policy_check to verify policy enforcement for
  options-based devices
- Update TEST_HW_DEVICE with realistic major/minor attributes (166:0 for tty)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: mock HostFeature.OS_AGENT in hardware event tests

The _hardware_events method has a @Job decorator with conditions=[JobCondition.OS_AGENT],
which checks if HostFeature.OS_AGENT is in sys_host.features. Without mocking this,
the job conditions fail and the hardware event handler is never invoked, causing
add_devices_allowed to not be called and tests to fail.

Add patch.object(type(coresys.host), "features", ...) to all four hardware event tests
to ensure the OS_AGENT job condition is met.

Fixes test failures:
- test_app_new_device (all 6 parametrized cases)
- test_app_new_device_no_haos
- test_app_options_device_hw_listener
- test_app_options_device_policy_check

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: fix TEST_DEV_PATH to match TEST_HW_DEVICE.path

TEST_DEV_PATH was set to /dev/ttyAMA0 but TEST_HW_DEVICE.path is /dev/ttyACM0.
This mismatch would cause the dev_path=TEST_DEV_PATH parametrized test cases
to fail because the hardware event handler checks if the device path intersects
with the app's allowed devices, and "/dev/ttyAMA0" != "/dev/ttyACM0".

Update TEST_DEV_PATH from /dev/ttyAMA0 to /dev/ttyACM0 to match TEST_HW_DEVICE.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: mock option_device_paths in test_app_options_device_hw_listener

The test sets up schema and options but option_device_paths property may not
be working as expected in the test environment. Add an explicit mock for
option_device_paths to ensure it returns the by-id path, guaranteeing that:
1. The hardware listener is registered (checks option_device_paths at registration)
2. The device path matching works correctly in _hardware_events

This ensures the test properly validates that hardware events are processed
for options-based devices after re-enumeration.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: make policy check test actually exercise the policy guard

test_app_options_device_policy_check set the device option via
persist["options"], but option_device_paths reads the merged options and
does not pick that override up during the test, so it returned an empty
set. The hardware event therefore failed the path-match guard and returned
before reaching the allowed_for_access check. The assert_not_called()
assertion then passed regardless of the policy outcome -- it would still
pass if the policy guard were removed entirely.

Mock option_device_paths to return the configured by-id path (mirroring
test_app_options_device_hw_listener) so the event device matches and
execution actually reaches the policy guard the test is meant to verify.

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

* test: add unit test for AppOptions.extract_device_paths

The integration tests exercise extract_device_paths only through a mocked
option_device_paths property, so the schema-walking logic introduced for
the hardware-event matching had no direct coverage.

Add a unit test that drives every schema shape the recursion handles --
flat, optional, filtered, list, nested dict and list of dicts -- and
asserts that non-device options, unset keys and empty values are skipped,
without requiring the devices to exist in hardware.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
2026-06-15 22:49:49 +02:00
355396aeab Migrate addon/addons config paths and schema names to app/apps (#6865)
* Migrate config file and directory paths from addons to apps

- Rename addons.json -> apps.json (FILE_HASSIO_APPS constant)
- Rename addons/{core,data,local,git} -> apps/{core,data,local,git}
- Rename addon_configs -> app_configs

Backwards compatibility: on startup, Supervisor checks for legacy
paths and renames them if the new paths don't already exist.
- addons.json migration runs in AppManager.load_config (executor)
- Directory migrations run in bootstrap before initialize_system (executor)

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

* Rename SCHEMA_ADDON(S)_* constants to SCHEMA_APP(S)_* in apps/validate.py

Update all references in supervisor and tests.

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

* Fix remaining test references to legacy addons/* paths

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

* Opportunistic remove of addons dir since it should be empty post migration

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-27 09:47:32 +02:00
Stefan AgnerandGitHub d3028d7bfc Enable flake8-pyi, flake8-return, flake8-raise ruff rules (#6861)
Enable the PYI, RET and RSE ruff rule sets and fix the resulting
violations across the codebase. The pylint no-else-* checks
(RET505-508) are now disabled since ruff covers them.

The cleanups are mechanical:

- RSE102: drop empty parentheses from `raise Exception()` when no
  arguments are passed.
- RET505-508: drop `else` branches that follow a `return`, `raise`,
  `continue` or `break`, flattening control flow.
- RET502/504: add explicit return values and remove redundant
  assign-then-return patterns.
- PYI030/032/041: tidy up type annotations (collapse literal unions,
  use `object` for `__eq__`/`__ne__`, drop redundant numeric unions).
2026-05-22 11:04:49 +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