Compare commits

...

62 Commits

Author SHA1 Message Date
Mike Degatano
b7c53d9e40 No update available if update cannot be installed on system 2024-06-12 15:58:30 -04:00
dependabot[bot]
b684c8673e Bump sentry-sdk from 2.5.0 to 2.5.1 (#5130)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.5.0 to 2.5.1.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.5.0...2.5.1)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 09:01:06 +02:00
dependabot[bot]
547f42439d Bump typing-extensions from 4.12.1 to 4.12.2 (#5129)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 08:49:42 +02:00
dependabot[bot]
c51ceb000f Bump sentry-sdk from 2.4.0 to 2.5.0 (#5126)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-07 09:19:18 +02:00
dependabot[bot]
4cbede1bc8 Bump pylint from 3.2.2 to 3.2.3 (#5127)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-07 09:01:27 +02:00
dependabot[bot]
5eac8c7780 Bump ruff from 0.4.7 to 0.4.8 (#5125)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.7 to 0.4.8.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.4.7...v0.4.8)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-06 08:37:25 +02:00
Mike Degatano
ab78d87304 Add safe mode option to core rebuild (#5120)
* Add safe mode option to core rebuild

* Adding logging for increased traceability
2024-06-05 15:44:07 -04:00
dependabot[bot]
09166e3867 Bump cryptography from 42.0.7 to 42.0.8 (#5121)
Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.7 to 42.0.8.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/42.0.7...42.0.8)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-05 15:38:13 -04:00
dependabot[bot]
8a5c813cdd Bump sentry-sdk from 2.3.1 to 2.4.0 (#5123)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.3.1 to 2.4.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.3.1...2.4.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-05 15:37:41 -04:00
dependabot[bot]
4200622f43 Bump pytest from 8.2.1 to 8.2.2 (#5122)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-05 08:43:01 +02:00
Mike Degatano
c4452a85b4 Fix addon in wrong state after restore (#5111)
* Fix addon in wrong state after restore

* Do not stop docker monitor for a shutdown
2024-06-04 16:17:43 +02:00
Mike Degatano
e57de4a3c1 Add uninstall addon suggestion to detached_addon_removed (#5105) 2024-06-03 10:38:34 -04:00
dependabot[bot]
9fd2c91c55 Bump ruff from 0.4.6 to 0.4.7 (#5116)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-03 10:29:23 +02:00
dependabot[bot]
fbd70013a8 Bump typing-extensions from 4.12.0 to 4.12.1 (#5117)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-03 09:54:10 +02:00
dependabot[bot]
8d18f3e66e Bump requests from 2.32.2 to 2.32.3 (#5115)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-30 08:52:20 +02:00
Richard P
5f5754e860 Add Exception handling when processing udev devices (#5088)
* Add Exception handling to UDEV reading and parsing

Khadas VIM4 UDEV returns something that python crapps its pants about. The exception just allows it to continue.

* Add an exception print

Added an exception print for the times things go bad.

* Split exception handling

The exception is not fatal when parsing error happens on one node. print it and continue.

* cleanups

* swapped functions

device.device_node   function bails very badly!  It raises no exceptions to the top but complains and errors

* Update supervisor/hardware/manager.py

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

* Update supervisor/hardware/manager.py

---------

Co-authored-by: Stefan Agner <stefan@agner.ch>
Co-authored-by: Pascal Vizeli <pvizeli@syshack.ch>
2024-05-29 15:33:15 -04:00
dependabot[bot]
974c882b9a Bump docker/login-action from 3.1.0 to 3.2.0 (#5114)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-29 08:50:25 +02:00
dependabot[bot]
a9ea90096b Bump ruff from 0.4.5 to 0.4.6 (#5113)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-29 08:26:23 +02:00
dependabot[bot]
45c72c426e Bump coverage from 7.5.2 to 7.5.3 (#5112)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-29 08:26:10 +02:00
dependabot[bot]
4e5b75fe19 Bump coverage from 7.5.1 to 7.5.2 (#5109)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 08:37:29 +02:00
dependabot[bot]
3cd617e68f --- (#5099)
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-24 09:33:15 +02:00
dependabot[bot]
ddff02f73b Bump docker from 7.0.0 to 7.1.0 (#5106)
Bumps [docker](https://github.com/docker/docker-py) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/docker/docker-py/releases)
- [Commits](https://github.com/docker/docker-py/compare/7.0.0...7.1.0)

---
updated-dependencies:
- dependency-name: docker
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-24 08:52:21 +02:00
dependabot[bot]
b59347b3d3 Bump typing-extensions from 4.11.0 to 4.12.0 (#5107)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-24 08:32:48 +02:00
dependabot[bot]
1dc769076f Bump sentry-sdk from 2.2.1 to 2.3.1 (#5108)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-24 08:25:53 +02:00
Mike Degatano
f150a19c0f Create issue for detached addons (#5084)
* Create issue for detached addons

* Separate issues into missing and removed
2024-05-23 09:36:59 +02:00
dependabot[bot]
c4bc1e3824 Bump ruff from 0.4.4 to 0.4.5 (#5103)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-23 08:33:52 +02:00
Mike Degatano
eca99b69db Max retries for auto applying addon image fixup (#5051)
* Max retries for auto applying addon image fixup

* Update supervisor/resolution/fixups/addon_execute_repair.py

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

---------

Co-authored-by: Stefan Agner <stefan@agner.ch>
2024-05-22 11:54:02 +02:00
dependabot[bot]
043af72847 Bump setuptools from 69.5.1 to 70.0.0 (#5100)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 09:33:26 +02:00
dependabot[bot]
05c7b6c639 Bump sentry-sdk from 2.2.0 to 2.2.1 (#5101)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 08:16:31 +02:00
dependabot[bot]
3385c99f1f --- (#5095)
updated-dependencies:
- dependency-name: dbus-fast
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 17:06:19 +02:00
dependabot[bot]
895117f857 --- (#5094)
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 17:05:23 +02:00
dependabot[bot]
9e3135e2de --- (#5093)
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 17:05:00 +02:00
Stefan Agner
9a1c517437 Pin Python requets package (#5097)
Make sure Python requests package is pinned to a known version. This is
required by docker-py for instance. This fixes current CI builds.
2024-05-21 14:52:24 +02:00
Mike Degatano
c0c0c4b7ad Fix doc and changelog API response for orphaned addons (#5082)
* Fix doc and changelog API response for orphaned addons

* Use correct terminology in tests
2024-05-21 09:02:01 +02:00
dependabot[bot]
be6e39fed0 Bump pylint from 3.1.1 to 3.2.1 (#5090)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 09:30:42 +02:00
dependabot[bot]
b384921ee0 Bump pytest from 8.2.0 to 8.2.1 (#5089)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 09:29:26 +02:00
dependabot[bot]
0d05a6eae3 Bump sentry-sdk from 2.1.1 to 2.2.0 (#5085)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-17 15:19:01 +02:00
dependabot[bot]
430aef68c6 Bump actions/checkout from 4.1.5 to 4.1.6 (#5086)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-17 10:40:37 +02:00
Stefan Agner
eac6070e12 Don't process hardware events when landing page is running (#5079) 2024-05-15 08:56:37 +02:00
dependabot[bot]
6693b7c2e6 Bump codecov/codecov-action from 4.3.1 to 4.4.0 (#5080)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-15 08:49:07 +02:00
dependabot[bot]
7898c3e433 Bump pylint from 3.1.0 to 3.1.1 (#5078)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 10:11:19 +02:00
dependabot[bot]
420ecd064e Bump pyudev from 0.24.1 to 0.24.3 (#5074)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 09:29:49 +02:00
dependabot[bot]
4289be53f8 Bump pre-commit from 3.7.0 to 3.7.1 (#5075)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 09:14:14 +02:00
dependabot[bot]
29b41b564e Bump ruff from 0.4.3 to 0.4.4 (#5072)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.4.3...v0.4.4)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-10 12:48:18 +02:00
dependabot[bot]
998eb69583 Bump dbus-fast from 2.21.1 to 2.21.2 (#5071)
Bumps [dbus-fast](https://github.com/bluetooth-devices/dbus-fast) from 2.21.1 to 2.21.2.
- [Release notes](https://github.com/bluetooth-devices/dbus-fast/releases)
- [Changelog](https://github.com/Bluetooth-Devices/dbus-fast/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bluetooth-devices/dbus-fast/compare/v2.21.1...v2.21.2)

---
updated-dependencies:
- dependency-name: dbus-fast
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-10 12:48:09 +02:00
Stefan Agner
8ebc097ff4 Revert "Bump orjson from 3.9.15 to 3.10.3 (#5057)" (#5068)
This reverts commit 71e91328f1.

It is not proven that the segfaults seen in Core are resolved. Since
we are shipping a bugfix release, conservatively revert back to 3.9.15.
2024-05-07 16:02:34 +02:00
Mike Degatano
c05984ca49 Fix no changelog API response (#5064)
* Fix no changelog API response

* Add comment reasoning HTTP 200 for no changelog

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

* 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>
Co-authored-by: Stefan Agner <stefan@agner.ch>
2024-05-07 10:41:16 +02:00
dependabot[bot]
1a700c3013 Bump sentry-sdk from 2.0.1 to 2.1.1 (#5067)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.0.1 to 2.1.1.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.0.1...2.1.1)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 10:17:12 +02:00
dependabot[bot]
a9c92cdec8 Bump cryptography from 42.0.6 to 42.0.7 (#5066)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 09:23:56 +02:00
dependabot[bot]
da8b938d5b Bump actions/checkout from 4.1.4 to 4.1.5 (#5065) 2024-05-07 08:26:00 +02:00
dependabot[bot]
71e91328f1 Bump orjson from 3.9.15 to 3.10.3 (#5057)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 22:46:12 +02:00
dependabot[bot]
6356be4c52 Bump jinja2 from 3.1.3 to 3.1.4 (#5061)
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.3...3.1.4)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 11:14:01 +02:00
dependabot[bot]
e26e5440b6 Bump ruff from 0.4.2 to 0.4.3 (#5060)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.2 to 0.4.3.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.4.2...v0.4.3)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 11:13:04 +02:00
dependabot[bot]
fecfbd1a3e Bump coverage from 7.5.0 to 7.5.1 (#5059)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.5.0 to 7.5.1.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.5.0...7.5.1)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 11:12:49 +02:00
dependabot[bot]
c00d6dfc76 Bump cryptography from 42.0.5 to 42.0.6 (#5058) 2024-05-06 10:00:57 +02:00
dependabot[bot]
85be66d90d Bump codecov/codecov-action from 4.3.0 to 4.3.1 (#5054)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 09:02:54 +02:00
Mike Degatano
1ac506b391 Skip udisks listener on failure to connect (#5049) 2024-05-01 10:50:48 +02:00
dependabot[bot]
f7738b77de Bump pytest from 8.1.1 to 8.2.0 (#5046)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.1.1 to 8.2.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.1.1...8.2.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 15:55:34 -04:00
Mike Degatano
824037bb7d Bump pytest-asyncio to 0.23.6 (#5048) 2024-04-30 15:46:44 -04:00
Stefan Agner
221292ad14 Mark issues in FixupBase abstract (#5033)
Since #5024 all fixups have an associated issue. Generally, it is
generally better to have an issue for every fixup so that things
can be mapped to repairs in Core easily. Let's mark the issues property
as abstract to indicate subclasses are required to implement it.
2024-04-29 16:24:58 -04:00
dependabot[bot]
16f8c75e9f Bump sentry-sdk from 1.45.0 to 2.0.1 (#5047)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-29 17:33:36 +02:00
dependabot[bot]
90a37079f1 Bump ruff from 0.4.1 to 0.4.2 (#5043)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 10:07:04 +02:00
29 changed files with 680 additions and 58 deletions

View File

@@ -53,7 +53,7 @@ jobs:
requirements: ${{ steps.requirements.outputs.changed }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
@@ -92,7 +92,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
@@ -149,7 +149,7 @@ jobs:
- name: Login to GitHub Container Registry
if: needs.init.outputs.publish == 'true'
uses: docker/login-action@v3.1.0
uses: docker/login-action@v3.2.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -178,7 +178,7 @@ jobs:
steps:
- name: Checkout the repository
if: needs.init.outputs.publish == 'true'
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Initialize git
if: needs.init.outputs.publish == 'true'
@@ -203,7 +203,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Build the Supervisor
if: needs.init.outputs.publish != 'true'

View File

@@ -25,7 +25,7 @@ jobs:
name: Prepare Python dependencies
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Set up Python
id: python
uses: actions/setup-python@v5.1.0
@@ -67,7 +67,7 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.1.0
id: python
@@ -110,7 +110,7 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.1.0
id: python
@@ -153,7 +153,7 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Register hadolint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
@@ -168,7 +168,7 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.1.0
id: python
@@ -212,7 +212,7 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.1.0
id: python
@@ -256,7 +256,7 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.1.0
id: python
@@ -288,7 +288,7 @@ jobs:
name: Run tests Python ${{ needs.prepare.outputs.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.1.0
id: python
@@ -346,7 +346,7 @@ jobs:
needs: ["pytest", "prepare"]
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.1.0
id: python
@@ -373,4 +373,4 @@ jobs:
coverage report
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4.3.0
uses: codecov/codecov-action@v4.4.1

View File

@@ -11,7 +11,7 @@ jobs:
name: Release Drafter
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
with:
fetch-depth: 0

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.4
uses: actions/checkout@v4.1.6
- name: Sentry Release
uses: getsentry/action-release@v1.7.0
env:

View File

@@ -8,22 +8,23 @@ brotli==1.1.0
ciso8601==2.3.1
colorlog==6.8.2
cpe==1.2.1
cryptography==42.0.5
cryptography==42.0.8
debugpy==1.8.1
deepmerge==1.1.1
dirhash==0.4.0
docker==7.0.0
docker==7.1.0
faust-cchardet==2.1.19
gitpython==3.1.43
jinja2==3.1.3
jinja2==3.1.4
orjson==3.9.15
pulsectl==24.4.0
pyudev==0.24.1
pyudev==0.24.3
PyYAML==6.0.1
requests==2.32.3
securetar==2024.2.1
sentry-sdk==1.45.0
setuptools==69.5.1
sentry-sdk==2.5.1
setuptools==70.0.0
voluptuous==0.14.2
dbus-fast==2.21.1
typing_extensions==4.11.0
dbus-fast==2.21.3
typing_extensions==4.12.2
zlib-fast==0.2.0

View File

@@ -1,12 +1,12 @@
coverage==7.5.0
pre-commit==3.7.0
pylint==3.1.0
coverage==7.5.3
pre-commit==3.7.1
pylint==3.2.3
pytest-aiohttp==1.0.5
pytest-asyncio==0.23.5
pytest-asyncio==0.23.6
pytest-cov==5.0.0
pytest-timeout==2.3.1
pytest==8.1.1
ruff==0.4.1
pytest==8.2.2
ruff==0.4.8
time-machine==2.14.1
typing_extensions==4.11.0
typing_extensions==4.12.2
urllib3==2.2.1

View File

@@ -285,9 +285,13 @@ class Addon(AddonModel):
@property
def need_update(self) -> bool:
"""Return True if an update is available."""
if self.is_detached:
if self.is_detached or self.version == self.latest_version:
return False
return self.version != self.latest_version
with suppress(AddonsNotSupportedError):
self._validate_availability(self.data_store)
return True
return False
@property
def dns(self) -> list[str]:
@@ -1343,11 +1347,11 @@ class Addon(AddonModel):
)
raise AddonsError() from err
finally:
# Is add-on loaded
if not self.loaded:
await self.load()
finally:
# Run add-on
if data[ATTR_STATE] == AddonState.STARTED:
wait_for_start = await self.start()

View File

@@ -697,7 +697,6 @@ class RestAPI(CoreSysAttributes):
web.get("/store", api_store.store_info),
web.get("/store/addons", api_store.addons_list),
web.get("/store/addons/{addon}", api_store.addons_addon_info),
web.get("/store/addons/{addon}/{version}", api_store.addons_addon_info),
web.get("/store/addons/{addon}/icon", api_store.addons_addon_icon),
web.get("/store/addons/{addon}/logo", api_store.addons_addon_logo),
web.get(
@@ -719,6 +718,8 @@ class RestAPI(CoreSysAttributes):
"/store/addons/{addon}/update/{version}",
api_store.addons_addon_update,
),
# Must be below others since it has a wildcard in resource path
web.get("/store/addons/{addon}/{version}", api_store.addons_addon_info),
web.post("/store/reload", api_store.reload),
web.get("/store/repositories", api_store.repositories_list),
web.get(

View File

@@ -182,9 +182,13 @@ class APIHomeAssistant(CoreSysAttributes):
)
@api_process
def rebuild(self, request: web.Request) -> Awaitable[None]:
async def rebuild(self, request: web.Request) -> None:
"""Rebuild Home Assistant."""
return asyncio.shield(self.sys_homeassistant.core.rebuild())
body = await api_validate(SCHEMA_RESTART, request)
await asyncio.shield(
self.sys_homeassistant.core.rebuild(safe_mode=body[ATTR_SAFE_MODE])
)
@api_process
async def check(self, request: web.Request) -> None:

View File

@@ -249,9 +249,14 @@ class APIStore(CoreSysAttributes):
@api_process_raw(CONTENT_TYPE_TEXT)
async def addons_addon_changelog(self, request: web.Request) -> str:
"""Return changelog from add-on."""
addon = self._extract_addon(request)
# Frontend can't handle error response here, need to return 200 and error as text for now
try:
addon = self._extract_addon(request)
except APIError as err:
return str(err)
if not addon.with_changelog:
raise APIError(f"No changelog found for add-on {addon.slug}!")
return f"No changelog found for add-on {addon.slug}!"
with addon.path_changelog.open("r") as changelog:
return changelog.read()
@@ -259,9 +264,14 @@ class APIStore(CoreSysAttributes):
@api_process_raw(CONTENT_TYPE_TEXT)
async def addons_addon_documentation(self, request: web.Request) -> str:
"""Return documentation from add-on."""
addon = self._extract_addon(request)
# Frontend can't handle error response here, need to return 200 and error as text for now
try:
addon = self._extract_addon(request)
except APIError as err:
return str(err)
if not addon.with_documentation:
raise APIError(f"No documentation found for add-on {addon.slug}!")
return f"No documentation found for add-on {addon.slug}!"
with addon.path_documentation.open("r") as documentation:
return documentation.read()

View File

@@ -345,9 +345,6 @@ class Core(CoreSysAttributes):
if self.state == CoreState.RUNNING:
self.state = CoreState.SHUTDOWN
# Stop docker monitoring
await self.sys_docker.unload()
# Shutdown Application Add-ons, using Home Assistant API
await self.sys_addons.shutdown(AddonStartup.APPLICATION)

View File

@@ -71,14 +71,14 @@ class UDisks2Manager(DBusInterfaceProxy):
_LOGGER.warning(
"No udisks2 support on the host. Host control has been disabled."
)
# Register for signals on devices added/removed
self.udisks2_object_manager.dbus.object_manager.on_interfaces_added(
self._interfaces_added
)
self.udisks2_object_manager.dbus.object_manager.on_interfaces_removed(
self._interfaces_removed
)
else:
# Register for signals on devices added/removed
self.udisks2_object_manager.dbus.object_manager.on_interfaces_added(
self._interfaces_added
)
self.udisks2_object_manager.dbus.object_manager.on_interfaces_removed(
self._interfaces_removed
)
@dbus_connected
async def update(self, changed: dict[str, Any] | None = None) -> None:

View File

@@ -103,7 +103,13 @@ class HardwareManager(CoreSysAttributes):
# Exctract all devices
for device in self._udev.list_devices():
# Skip devices without mapping
if not device.device_node or self.helper.hide_virtual_device(device):
try:
if not device.device_node or self.helper.hide_virtual_device(device):
continue
except UnicodeDecodeError as err:
# Some udev properties have an unkown/different encoding. This is a general
# problem with pyudev, see https://github.com/pyudev/pyudev/pull/230
_LOGGER.warning("Ignoring udev device due to error: %s", err)
continue
self._devices[device.sys_name] = Device.import_udev(device)

View File

@@ -367,6 +367,7 @@ class HomeAssistantCore(JobGroup):
"""Restart Home Assistant Docker."""
# Create safe mode marker file if necessary
if safe_mode:
_LOGGER.debug("Creating safe mode marker file.")
await self.sys_run_in_executor(
(self.sys_config.path_homeassistant / SAFE_MODE_FILENAME).touch
)
@@ -383,8 +384,15 @@ class HomeAssistantCore(JobGroup):
limit=JobExecutionLimit.GROUP_ONCE,
on_condition=HomeAssistantJobError,
)
async def rebuild(self) -> None:
async def rebuild(self, *, safe_mode: bool = False) -> None:
"""Rebuild Home Assistant Docker container."""
# Create safe mode marker file if necessary
if safe_mode:
_LOGGER.debug("Creating safe mode marker file.")
await self.sys_run_in_executor(
(self.sys_config.path_homeassistant / SAFE_MODE_FILENAME).touch
)
with suppress(DockerError):
await self.instance.stop()
await self.start()

View File

@@ -48,7 +48,7 @@ from ..utils import remove_folder
from ..utils.common import FileConfiguration
from ..utils.json import read_json_file, write_json_file
from .api import HomeAssistantAPI
from .const import ATTR_OVERRIDE_IMAGE, WSType
from .const import ATTR_OVERRIDE_IMAGE, LANDINGPAGE, WSType
from .core import HomeAssistantCore
from .secrets import HomeAssistantSecrets
from .validate import SCHEMA_HASS_CONFIG
@@ -328,6 +328,7 @@ class HomeAssistant(FileConfiguration, CoreSysAttributes):
if (
not self.sys_hardware.policy.is_match_cgroup(PolicyGroup.UART, device)
or not self.version
or self.version == LANDINGPAGE
or self.version < "2021.9.0"
):
return

View File

@@ -0,0 +1,49 @@
"""Helpers to check for detached addons due to repo misisng."""
from ...const import CoreState
from ...coresys import CoreSys
from ..const import ContextType, IssueType
from .base import CheckBase
def setup(coresys: CoreSys) -> CheckBase:
"""Check setup function."""
return CheckDetachedAddonMissing(coresys)
class CheckDetachedAddonMissing(CheckBase):
"""CheckDetachedAddonMissing class for check."""
async def run_check(self) -> None:
"""Run check if not affected by issue."""
for addon in self.sys_addons.installed:
if (
addon.is_detached
and addon.repository not in self.sys_store.repositories
):
self.sys_resolution.create_issue(
IssueType.DETACHED_ADDON_MISSING,
ContextType.ADDON,
reference=addon.slug,
)
async def approve_check(self, reference: str | None = None) -> bool:
"""Approve check if it is affected by issue."""
return (
addon := self.sys_addons.get(reference, local_only=True)
) and addon.is_detached
@property
def issue(self) -> IssueType:
"""Return a IssueType enum."""
return IssueType.DETACHED_ADDON_MISSING
@property
def context(self) -> ContextType:
"""Return a ContextType enum."""
return ContextType.ADDON
@property
def states(self) -> list[CoreState]:
"""Return a list of valid states when this check can run."""
return [CoreState.SETUP]

View File

@@ -0,0 +1,47 @@
"""Helpers to check for detached addons due to removal from repo."""
from ...const import CoreState
from ...coresys import CoreSys
from ..const import ContextType, IssueType, SuggestionType
from .base import CheckBase
def setup(coresys: CoreSys) -> CheckBase:
"""Check setup function."""
return CheckDetachedAddonRemoved(coresys)
class CheckDetachedAddonRemoved(CheckBase):
"""CheckDetachedAddonRemoved class for check."""
async def run_check(self) -> None:
"""Run check if not affected by issue."""
for addon in self.sys_addons.installed:
if addon.is_detached and addon.repository in self.sys_store.repositories:
self.sys_resolution.create_issue(
IssueType.DETACHED_ADDON_REMOVED,
ContextType.ADDON,
reference=addon.slug,
suggestions=[SuggestionType.EXECUTE_REMOVE],
)
async def approve_check(self, reference: str | None = None) -> bool:
"""Approve check if it is affected by issue."""
return (
addon := self.sys_addons.get(reference, local_only=True)
) and addon.is_detached
@property
def issue(self) -> IssueType:
"""Return a IssueType enum."""
return IssueType.DETACHED_ADDON_REMOVED
@property
def context(self) -> ContextType:
"""Return a ContextType enum."""
return ContextType.ADDON
@property
def states(self) -> list[CoreState]:
"""Return a list of valid states when this check can run."""
return [CoreState.SETUP]

View File

@@ -73,6 +73,8 @@ class IssueType(StrEnum):
CORRUPT_DOCKER = "corrupt_docker"
CORRUPT_REPOSITORY = "corrupt_repository"
CORRUPT_FILESYSTEM = "corrupt_filesystem"
DETACHED_ADDON_MISSING = "detached_addon_missing"
DETACHED_ADDON_REMOVED = "detached_addon_removed"
DISABLED_DATA_DISK = "disabled_data_disk"
DNS_LOOP = "dns_loop"
DNS_SERVER_FAILED = "dns_server_failed"

View File

@@ -0,0 +1,52 @@
"""Helpers to fix addon issue by removing it."""
import logging
from ...coresys import CoreSys
from ...exceptions import AddonsError, ResolutionFixupError
from ..const import ContextType, IssueType, SuggestionType
from .base import FixupBase
_LOGGER: logging.Logger = logging.getLogger(__name__)
def setup(coresys: CoreSys) -> FixupBase:
"""Check setup function."""
return FixupAddonExecuteRemove(coresys)
class FixupAddonExecuteRemove(FixupBase):
"""Storage class for fixup."""
async def process_fixup(self, reference: str | None = None) -> None:
"""Initialize the fixup class."""
if not (addon := self.sys_addons.get(reference, local_only=True)):
_LOGGER.info("Addon %s already removed", reference)
return
# Remove addon
_LOGGER.info("Remove addon: %s", reference)
try:
addon.uninstall()
except AddonsError as err:
_LOGGER.error("Could not remove %s due to %s", reference, err)
raise ResolutionFixupError() from None
@property
def suggestion(self) -> SuggestionType:
"""Return a SuggestionType enum."""
return SuggestionType.EXECUTE_REMOVE
@property
def context(self) -> ContextType:
"""Return a ContextType enum."""
return ContextType.ADDON
@property
def issues(self) -> list[IssueType]:
"""Return a IssueType enum list."""
return [IssueType.DETACHED_ADDON_REMOVED]
@property
def auto(self) -> bool:
"""Return if a fixup can be apply as auto fix."""
return False

View File

@@ -7,6 +7,7 @@ from ..const import ContextType, IssueType, SuggestionType
from .base import FixupBase
_LOGGER: logging.Logger = logging.getLogger(__name__)
MAX_AUTO_ATTEMPTS = 5
def setup(coresys: CoreSys) -> FixupBase:
@@ -17,6 +18,11 @@ def setup(coresys: CoreSys) -> FixupBase:
class FixupAddonExecuteRepair(FixupBase):
"""Storage class for fixup."""
def __init__(self, coresys: CoreSys) -> None:
"""Initialize the add-on execute repair fixup class."""
super().__init__(coresys)
self.attempts = 0
async def process_fixup(self, reference: str | None = None) -> None:
"""Pull the addons image."""
addon = self.sys_addons.get(reference, local_only=True)
@@ -34,6 +40,7 @@ class FixupAddonExecuteRepair(FixupBase):
return
_LOGGER.info("Installing image for addon %s")
self.attempts += 1
await addon.instance.install(addon.version)
@property
@@ -54,4 +61,4 @@ class FixupAddonExecuteRepair(FixupBase):
@property
def auto(self) -> bool:
"""Return if a fixup can be apply as auto fix."""
return True
return self.attempts < MAX_AUTO_ATTEMPTS

View File

@@ -58,9 +58,9 @@ class FixupBase(ABC, CoreSysAttributes):
"""Return a ContextType enum."""
@property
@abstractmethod
def issues(self) -> list[IssueType]:
"""Return a IssueType enum list."""
return []
@property
def auto(self) -> bool:

View File

@@ -4,6 +4,8 @@ import asyncio
from unittest.mock import MagicMock, PropertyMock, patch
from aiohttp.test_utils import TestClient
from awesomeversion import AwesomeVersion
import pytest
from supervisor.addons.addon import Addon
from supervisor.addons.build import AddonBuild
@@ -285,3 +287,37 @@ async def test_api_addon_uninstall_remove_config(
assert resp.status == 200
assert not coresys.addons.get("local_example", local_only=True)
assert not test_folder.exists()
async def test_api_update_available_validates_version(
api_client: TestClient,
coresys: CoreSys,
install_addon_example: Addon,
caplog: pytest.LogCaptureFixture,
tmp_supervisor_data,
path_extern,
):
"""Test update available field is only true if user can update to latest version."""
install_addon_example.data["ingress"] = False
install_addon_example.data_store["version"] = "1.3.0"
caplog.clear()
resp = await api_client.get("/addons/local_example/info")
assert resp.status == 200
result = await resp.json()
assert result["data"]["version"] == "1.2.0"
assert result["data"]["version_latest"] == "1.3.0"
assert result["data"]["update_available"] is True
# If new version can't be installed due to HA version, then no update is available
coresys.homeassistant.version = AwesomeVersion("2024.04.0")
install_addon_example.data_store["homeassistant"] = "2024.06.0"
resp = await api_client.get("/addons/local_example/info")
assert resp.status == 200
result = await resp.json()
assert result["data"]["version"] == "1.2.0"
assert result["data"]["version_latest"] == "1.3.0"
assert result["data"]["update_available"] is False
assert "Add-on local_example not supported" not in caplog.text

View File

@@ -4,6 +4,7 @@ from pathlib import Path
from unittest.mock import MagicMock, patch
from aiohttp.test_utils import TestClient
from awesomeversion import AwesomeVersion
import pytest
from supervisor.coresys import CoreSys
@@ -115,3 +116,29 @@ async def test_api_restart(
assert container.restart.call_count == 2
assert safe_mode_marker.exists()
async def test_api_rebuild(
api_client: TestClient,
coresys: CoreSys,
container: MagicMock,
tmp_supervisor_data: Path,
path_extern,
):
"""Test rebuilding homeassistant."""
coresys.homeassistant.version = AwesomeVersion("2023.09.0")
safe_mode_marker = tmp_supervisor_data / "homeassistant" / "safe-mode"
with patch.object(HomeAssistantCore, "_block_till_run"):
await api_client.post("/homeassistant/rebuild")
assert container.remove.call_count == 2
container.start.assert_called_once()
assert not safe_mode_marker.exists()
with patch.object(HomeAssistantCore, "_block_till_run"):
await api_client.post("/homeassistant/rebuild", json={"safe_mode": True})
assert container.remove.call_count == 4
assert container.start.call_count == 2
assert safe_mode_marker.exists()

View File

@@ -1,6 +1,7 @@
"""Test Store API."""
import asyncio
from pathlib import Path
from unittest.mock import MagicMock, PropertyMock, patch
from aiohttp.test_utils import TestClient
@@ -8,6 +9,7 @@ import pytest
from supervisor.addons.addon import Addon
from supervisor.arch import CpuArch
from supervisor.config import CoreConfig
from supervisor.const import AddonState
from supervisor.coresys import CoreSys
from supervisor.docker.addon import DockerAddon
@@ -188,3 +190,91 @@ async def test_api_store_update_healthcheck(
assert resp.status == 200
await _container_events_task
@pytest.mark.parametrize("resource", ["store/addons", "addons"])
async def test_api_store_addons_no_changelog(
api_client: TestClient, coresys: CoreSys, store_addon: AddonStore, resource: str
):
"""Test /store/addons/{addon}/changelog REST API.
Currently the frontend expects a valid body even in the error case. Make sure that is
what the API returns.
"""
assert store_addon.with_changelog is False
resp = await api_client.get(f"/{resource}/{store_addon.slug}/changelog")
assert resp.status == 200
result = await resp.text()
assert result == "No changelog found for add-on test_store_addon!"
@pytest.mark.parametrize("resource", ["store/addons", "addons"])
async def test_api_detached_addon_changelog(
api_client: TestClient,
coresys: CoreSys,
install_addon_ssh: Addon,
tmp_supervisor_data: Path,
resource: str,
):
"""Test /store/addons/{addon}/changelog for an detached addon.
Currently the frontend expects a valid body even in the error case. Make sure that is
what the API returns.
"""
(addons_dir := tmp_supervisor_data / "addons" / "local").mkdir()
with patch.object(
CoreConfig, "path_addons_local", new=PropertyMock(return_value=addons_dir)
):
await coresys.store.load()
assert install_addon_ssh.is_detached is True
assert install_addon_ssh.with_changelog is False
resp = await api_client.get(f"/{resource}/{install_addon_ssh.slug}/changelog")
assert resp.status == 200
result = await resp.text()
assert result == "Addon local_ssh with version latest does not exist in the store"
@pytest.mark.parametrize("resource", ["store/addons", "addons"])
async def test_api_store_addons_no_documentation(
api_client: TestClient, coresys: CoreSys, store_addon: AddonStore, resource: str
):
"""Test /store/addons/{addon}/documentation REST API.
Currently the frontend expects a valid body even in the error case. Make sure that is
what the API returns.
"""
assert store_addon.with_documentation is False
resp = await api_client.get(f"/{resource}/{store_addon.slug}/documentation")
assert resp.status == 200
result = await resp.text()
assert result == "No documentation found for add-on test_store_addon!"
@pytest.mark.parametrize("resource", ["store/addons", "addons"])
async def test_api_detached_addon_documentation(
api_client: TestClient,
coresys: CoreSys,
install_addon_ssh: Addon,
tmp_supervisor_data: Path,
resource: str,
):
"""Test /store/addons/{addon}/changelog for an detached addon.
Currently the frontend expects a valid body even in the error case. Make sure that is
what the API returns.
"""
(addons_dir := tmp_supervisor_data / "addons" / "local").mkdir()
with patch.object(
CoreConfig, "path_addons_local", new=PropertyMock(return_value=addons_dir)
):
await coresys.store.load()
assert install_addon_ssh.is_detached is True
assert install_addon_ssh.with_documentation is False
resp = await api_client.get(f"/{resource}/{install_addon_ssh.slug}/documentation")
assert resp.status == 200
result = await resp.text()
assert result == "Addon local_ssh with version latest does not exist in the store"

View File

@@ -1750,3 +1750,40 @@ async def test_reload_error(
assert "Could not list backups" in caplog.text
assert coresys.core.healthy is healthy_expected
async def test_monitoring_after_full_restore(
coresys: CoreSys, full_backup_mock, install_addon_ssh, container
):
"""Test monitoring of addon state still works after full restore."""
coresys.core.state = CoreState.RUNNING
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
coresys.homeassistant.core.start = AsyncMock(return_value=None)
coresys.homeassistant.core.stop = AsyncMock(return_value=None)
coresys.homeassistant.core.update = AsyncMock(return_value=None)
manager = BackupManager(coresys)
backup_instance = full_backup_mock.return_value
assert await manager.do_restore_full(backup_instance)
backup_instance.restore_addons.assert_called_once_with([TEST_ADDON_SLUG])
assert coresys.core.state == CoreState.RUNNING
coresys.docker.unload.assert_not_called()
async def test_monitoring_after_partial_restore(
coresys: CoreSys, partial_backup_mock, install_addon_ssh, container
):
"""Test monitoring of addon state still works after full restore."""
coresys.core.state = CoreState.RUNNING
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
manager = BackupManager(coresys)
backup_instance = partial_backup_mock.return_value
assert await manager.do_restore_partial(backup_instance, addons=[TEST_ADDON_SLUG])
backup_instance.restore_addons.assert_called_once_with([TEST_ADDON_SLUG])
assert coresys.core.state == CoreState.RUNNING
coresys.docker.unload.assert_not_called()

View File

@@ -0,0 +1,86 @@
"""Test check for detached addons due to repo missing."""
from unittest.mock import patch
from supervisor.addons.addon import Addon
from supervisor.const import CoreState
from supervisor.coresys import CoreSys
from supervisor.resolution.checks.detached_addon_missing import (
CheckDetachedAddonMissing,
)
from supervisor.resolution.const import ContextType, IssueType
async def test_base(coresys: CoreSys):
"""Test check basics."""
detached_addon_missing = CheckDetachedAddonMissing(coresys)
assert detached_addon_missing.slug == "detached_addon_missing"
assert detached_addon_missing.enabled
async def test_check(coresys: CoreSys, install_addon_ssh: Addon):
"""Test check for detached addons."""
detached_addon_missing = CheckDetachedAddonMissing(coresys)
coresys.core.state = CoreState.SETUP
await detached_addon_missing()
assert len(coresys.resolution.issues) == 0
# Mock test addon was been installed from a now non-existent store
install_addon_ssh.slug = "abc123_ssh"
coresys.addons.data.system["abc123_ssh"] = coresys.addons.data.system["local_ssh"]
coresys.addons.local["abc123_ssh"] = coresys.addons.local["local_ssh"]
install_addon_ssh.data["repository"] = "abc123"
await detached_addon_missing()
assert len(coresys.resolution.issues) == 1
assert coresys.resolution.issues[0].type is IssueType.DETACHED_ADDON_MISSING
assert coresys.resolution.issues[0].context is ContextType.ADDON
assert coresys.resolution.issues[0].reference == install_addon_ssh.slug
assert len(coresys.resolution.suggestions) == 0
async def test_approve(coresys: CoreSys, install_addon_ssh: Addon):
"""Test approve existing detached addon issues."""
detached_addon_missing = CheckDetachedAddonMissing(coresys)
coresys.core.state = CoreState.SETUP
assert (
await detached_addon_missing.approve_check(reference=install_addon_ssh.slug)
is False
)
# Mock test addon was been installed from a now non-existent store
install_addon_ssh.slug = "abc123_ssh"
coresys.addons.data.system["abc123_ssh"] = coresys.addons.data.system["local_ssh"]
coresys.addons.local["abc123_ssh"] = coresys.addons.local["local_ssh"]
install_addon_ssh.data["repository"] = "abc123"
assert (
await detached_addon_missing.approve_check(reference=install_addon_ssh.slug)
is True
)
async def test_did_run(coresys: CoreSys):
"""Test that the check ran as expected."""
detached_addon_missing = CheckDetachedAddonMissing(coresys)
should_run = detached_addon_missing.states
should_not_run = [state for state in CoreState if state not in should_run]
assert should_run == [CoreState.SETUP]
assert len(should_not_run) != 0
with patch.object(
CheckDetachedAddonMissing, "run_check", return_value=None
) as check:
for state in should_run:
coresys.core.state = state
await detached_addon_missing()
check.assert_called_once()
check.reset_mock()
for state in should_not_run:
coresys.core.state = state
await detached_addon_missing()
check.assert_not_called()
check.reset_mock()

View File

@@ -0,0 +1,97 @@
"""Test check for detached addons due to removal from repo."""
from pathlib import Path
from unittest.mock import PropertyMock, patch
from supervisor.addons.addon import Addon
from supervisor.config import CoreConfig
from supervisor.const import CoreState
from supervisor.coresys import CoreSys
from supervisor.resolution.checks.detached_addon_removed import (
CheckDetachedAddonRemoved,
)
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
async def test_base(coresys: CoreSys):
"""Test check basics."""
detached_addon_removed = CheckDetachedAddonRemoved(coresys)
assert detached_addon_removed.slug == "detached_addon_removed"
assert detached_addon_removed.enabled
async def test_check(
coresys: CoreSys, install_addon_ssh: Addon, tmp_supervisor_data: Path
):
"""Test check for detached addons."""
detached_addon_removed = CheckDetachedAddonRemoved(coresys)
coresys.core.state = CoreState.SETUP
await detached_addon_removed()
assert len(coresys.resolution.issues) == 0
assert len(coresys.resolution.suggestions) == 0
(addons_dir := tmp_supervisor_data / "addons" / "local").mkdir()
with patch.object(
CoreConfig, "path_addons_local", new=PropertyMock(return_value=addons_dir)
):
await coresys.store.load()
await detached_addon_removed()
assert len(coresys.resolution.issues) == 1
assert coresys.resolution.issues[0].type is IssueType.DETACHED_ADDON_REMOVED
assert coresys.resolution.issues[0].context is ContextType.ADDON
assert coresys.resolution.issues[0].reference == install_addon_ssh.slug
assert len(coresys.resolution.suggestions) == 1
assert coresys.resolution.suggestions[0].type is SuggestionType.EXECUTE_REMOVE
assert coresys.resolution.suggestions[0].context is ContextType.ADDON
assert coresys.resolution.suggestions[0].reference == install_addon_ssh.slug
async def test_approve(
coresys: CoreSys, install_addon_ssh: Addon, tmp_supervisor_data: Path
):
"""Test approve existing detached addon issues."""
detached_addon_removed = CheckDetachedAddonRemoved(coresys)
coresys.core.state = CoreState.SETUP
assert (
await detached_addon_removed.approve_check(reference=install_addon_ssh.slug)
is False
)
(addons_dir := tmp_supervisor_data / "addons" / "local").mkdir()
with patch.object(
CoreConfig, "path_addons_local", new=PropertyMock(return_value=addons_dir)
):
await coresys.store.load()
assert (
await detached_addon_removed.approve_check(reference=install_addon_ssh.slug)
is True
)
async def test_did_run(coresys: CoreSys):
"""Test that the check ran as expected."""
detached_addon_removed = CheckDetachedAddonRemoved(coresys)
should_run = detached_addon_removed.states
should_not_run = [state for state in CoreState if state not in should_run]
assert should_run == [CoreState.SETUP]
assert len(should_not_run) != 0
with patch.object(
CheckDetachedAddonRemoved, "run_check", return_value=None
) as check:
for state in should_run:
coresys.core.state = state
await detached_addon_removed()
check.assert_called_once()
check.reset_mock()
for state in should_not_run:
coresys.core.state = state
await detached_addon_removed()
check.assert_not_called()
check.reset_mock()

View File

@@ -0,0 +1,34 @@
"""Test evaluation base."""
from unittest.mock import patch
from supervisor.addons.addon import Addon
from supervisor.coresys import CoreSys
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
from supervisor.resolution.data import Issue, Suggestion
from supervisor.resolution.fixups.addon_execute_remove import FixupAddonExecuteRemove
async def test_fixup(coresys: CoreSys, install_addon_ssh: Addon):
"""Test fixup."""
addon_execute_remove = FixupAddonExecuteRemove(coresys)
assert addon_execute_remove.auto is False
coresys.resolution.suggestions = Suggestion(
SuggestionType.EXECUTE_REMOVE,
ContextType.ADDON,
reference=install_addon_ssh.slug,
)
coresys.resolution.issues = Issue(
IssueType.DETACHED_ADDON_REMOVED,
ContextType.ADDON,
reference=install_addon_ssh.slug,
)
with patch.object(Addon, "uninstall") as uninstall:
await addon_execute_remove()
assert uninstall.called
assert len(coresys.resolution.suggestions) == 0
assert len(coresys.resolution.issues) == 0

View File

@@ -3,12 +3,14 @@
from unittest.mock import MagicMock, patch
from docker.errors import NotFound
import pytest
from supervisor.addons.addon import Addon
from supervisor.coresys import CoreSys
from supervisor.docker.addon import DockerAddon
from supervisor.docker.interface import DockerInterface
from supervisor.docker.manager import DockerAPI
from supervisor.exceptions import DockerError
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
from supervisor.resolution.fixups.addon_execute_repair import FixupAddonExecuteRepair
@@ -35,6 +37,30 @@ async def test_fixup(docker: DockerAPI, coresys: CoreSys, install_addon_ssh: Add
assert not coresys.resolution.suggestions
async def test_fixup_max_auto_attempts(
docker: DockerAPI, coresys: CoreSys, install_addon_ssh: Addon
):
"""Test fixup stops being auto-applied after 5 failures."""
docker.images.get.side_effect = NotFound("missing")
install_addon_ssh.data["image"] = "test_image"
addon_execute_repair = FixupAddonExecuteRepair(coresys)
coresys.resolution.create_issue(
IssueType.MISSING_IMAGE,
ContextType.ADDON,
reference="local_ssh",
suggestions=[SuggestionType.EXECUTE_REPAIR],
)
with patch.object(DockerInterface, "install", side_effect=DockerError):
for _ in range(5):
assert addon_execute_repair.auto is True
with pytest.raises(DockerError):
await addon_execute_repair()
assert addon_execute_repair.auto is False
async def test_fixup_no_addon(coresys: CoreSys):
"""Test fixup dismisses if addon is missing."""
addon_execute_repair = FixupAddonExecuteRepair(coresys)