Compare commits

..

1 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
16 changed files with 97 additions and 107 deletions

View File

@@ -53,7 +53,7 @@ jobs:
requirements: ${{ steps.requirements.outputs.changed }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
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.7
uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
@@ -178,7 +178,7 @@ jobs:
steps:
- name: Checkout the repository
if: needs.init.outputs.publish == 'true'
uses: actions/checkout@v4.1.7
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.7
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.7
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.7
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.7
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.7
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.7
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.7
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.7
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.7
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.7
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.5.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.7
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.7
uses: actions/checkout@v4.1.6
- name: Sentry Release
uses: getsentry/action-release@v1.7.0
env:

View File

@@ -22,8 +22,8 @@ pyudev==0.24.3
PyYAML==6.0.1
requests==2.32.3
securetar==2024.2.1
sentry-sdk==2.6.0
setuptools==70.1.0
sentry-sdk==2.5.1
setuptools==70.0.0
voluptuous==0.14.2
dbus-fast==2.21.3
typing_extensions==4.12.2

View File

@@ -6,7 +6,7 @@ pytest-asyncio==0.23.6
pytest-cov==5.0.0
pytest-timeout==2.3.1
pytest==8.2.2
ruff==0.4.10
ruff==0.4.8
time-machine==2.14.1
typing_extensions==4.12.2
urllib3==2.2.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]:

View File

@@ -174,6 +174,17 @@ class Tasks(CoreSysAttributes):
self._cache[HASS_WATCHDOG_API_FAILURES] = 0
return
# Give up after 5 reanimation failures in a row. Supervisor cannot fix this issue.
reanimate_fails = self._cache.get(HASS_WATCHDOG_REANIMATE_FAILURES, 0)
if reanimate_fails >= HASS_WATCHDOG_MAX_REANIMATE_ATTEMPTS:
if reanimate_fails == HASS_WATCHDOG_MAX_REANIMATE_ATTEMPTS:
_LOGGER.critical(
"Watchdog cannot reanimate Home Assistant Core, failed all %s attempts.",
reanimate_fails,
)
self._cache[HASS_WATCHDOG_REANIMATE_FAILURES] += 1
return
# Init cache data
api_fails = self._cache.get(HASS_WATCHDOG_API_FAILURES, 0)
@@ -184,38 +195,16 @@ class Tasks(CoreSysAttributes):
_LOGGER.warning("Watchdog missed an Home Assistant Core API response.")
return
# After 5 reanimation attempts switch to safe mode. If that fails, give up
reanimate_fails = self._cache.get(HASS_WATCHDOG_REANIMATE_FAILURES, 0)
if reanimate_fails > HASS_WATCHDOG_MAX_REANIMATE_ATTEMPTS:
return
if safe_mode := reanimate_fails == HASS_WATCHDOG_MAX_REANIMATE_ATTEMPTS:
_LOGGER.critical(
"Watchdog cannot reanimate Home Assistant Core, failed all %s attempts. Restarting into safe mode",
reanimate_fails,
)
else:
_LOGGER.error(
"Watchdog missed %s Home Assistant Core API responses in a row. Restarting Home Assistant Core!",
HASS_WATCHDOG_MAX_API_ATTEMPTS,
)
_LOGGER.error(
"Watchdog missed %s Home Assistant Core API responses in a row. Restarting Home Assistant Core API!",
HASS_WATCHDOG_MAX_API_ATTEMPTS,
)
try:
if safe_mode:
await self.sys_homeassistant.core.rebuild(safe_mode=True)
else:
await self.sys_homeassistant.core.restart()
await self.sys_homeassistant.core.restart()
except HomeAssistantError as err:
if reanimate_fails == 0 or safe_mode:
_LOGGER.error("Home Assistant watchdog reanimation failed!")
if reanimate_fails == 0:
capture_exception(err)
if safe_mode:
_LOGGER.critical(
"Safe mode restart failed. Watchdog cannot bring Home Assistant online."
)
else:
_LOGGER.error("Home Assistant watchdog reanimation failed!")
self._cache[HASS_WATCHDOG_REANIMATE_FAILURES] = reanimate_fails + 1
else:
self._cache[HASS_WATCHDOG_REANIMATE_FAILURES] = 0

View File

@@ -26,7 +26,7 @@ class FixupAddonExecuteRemove(FixupBase):
# Remove addon
_LOGGER.info("Remove addon: %s", reference)
try:
await addon.uninstall(remove_config=False)
addon.uninstall()
except AddonsError as err:
_LOGGER.error("Could not remove %s due to %s", reference, err)
raise ResolutionFixupError() from None

View File

@@ -8,13 +8,11 @@ from .const import StoreType
URL_COMMUNITY_ADDONS = "https://github.com/hassio-addons/repository"
URL_ESPHOME = "https://github.com/esphome/home-assistant-addon"
URL_MUSIC_ASSISTANT = "https://github.com/music-assistant/home-assistant-addon"
BUILTIN_REPOSITORIES = {
StoreType.CORE,
StoreType.LOCAL,
URL_COMMUNITY_ADDONS,
URL_ESPHOME,
URL_MUSIC_ASSISTANT,
}
# pylint: disable=no-value-for-parameter

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

@@ -1,5 +0,0 @@
{
"name": "Music Assistant",
"url": "https://github.com/music-assistant/core",
"maintainer": "Music Assistant <marcelveldt@users.noreply.github.com>"
}

View File

@@ -46,7 +46,7 @@ async def test_watchdog_homeassistant_api(
restart.assert_called_once()
assert "Watchdog missed an Home Assistant Core API response." not in caplog.text
assert (
"Watchdog missed 2 Home Assistant Core API responses in a row. Restarting Home Assistant Core!"
"Watchdog missed 2 Home Assistant Core API responses in a row. Restarting Home Assistant Core API!"
in caplog.text
)
@@ -109,48 +109,31 @@ async def test_watchdog_homeassistant_api_reanimation_limit(
HomeAssistantAPI, "check_api_state", return_value=False
), patch.object(
HomeAssistantCore, "restart", side_effect=(err := HomeAssistantError())
) as restart, patch.object(
HomeAssistantCore, "rebuild", side_effect=err
) as rebuild:
) as restart:
for _ in range(5):
await tasks._watchdog_homeassistant_api()
restart.assert_not_called()
await tasks._watchdog_homeassistant_api()
restart.assert_called_once_with()
restart.assert_called_once()
assert "Home Assistant watchdog reanimation failed!" in caplog.text
rebuild.assert_not_called()
restart.reset_mock()
capture_exception.assert_called_once_with(err)
# Next time it should try safe mode
caplog.clear()
await tasks._watchdog_homeassistant_api()
rebuild.assert_not_called()
await tasks._watchdog_homeassistant_api()
rebuild.assert_called_once_with(safe_mode=True)
restart.assert_not_called()
assert "Watchdog missed an Home Assistant Core API response." not in caplog.text
assert "Watchdog found a problem with Home Assistant API!" not in caplog.text
assert (
"Watchdog cannot reanimate Home Assistant Core, failed all 5 attempts. Restarting into safe mode"
in caplog.text
)
assert (
"Safe mode restart failed. Watchdog cannot bring Home Assistant online."
"Watchdog cannot reanimate Home Assistant Core, failed all 5 attempts."
in caplog.text
)
# After safe mode has failed too, no more restart attempts
rebuild.reset_mock()
caplog.clear()
await tasks._watchdog_homeassistant_api()
assert "Watchdog missed an Home Assistant Core API response." in caplog.text
caplog.clear()
await tasks._watchdog_homeassistant_api()
restart.assert_not_called()
assert not caplog.text
restart.assert_not_called()
rebuild.assert_not_called()

View File

@@ -163,7 +163,6 @@ async def test_preinstall_valid_repository(
assert store_manager.get("local").validate()
assert store_manager.get("a0d7b954").validate()
assert store_manager.get("5c53de3b").validate()
assert store_manager.get("d5369777").validate()
@pytest.mark.parametrize("use_update", [True, False])

View File

@@ -39,11 +39,11 @@ async def test_default_load(coresys: CoreSys):
):
await store_manager.load()
assert len(store_manager.all) == 5
assert len(store_manager.all) == 4
assert isinstance(store_manager.get("core"), Repository)
assert isinstance(store_manager.get("local"), Repository)
assert len(store_manager.repository_urls) == 3
assert len(store_manager.repository_urls) == 2
assert (
"https://github.com/hassio-addons/repository" in store_manager.repository_urls
)
@@ -51,11 +51,6 @@ async def test_default_load(coresys: CoreSys):
"https://github.com/esphome/home-assistant-addon"
in store_manager.repository_urls
)
assert (
"https://github.com/music-assistant/home-assistant-addon"
in store_manager.repository_urls
)
# NOTE: When adding new stores, make sure to add it to tests/fixtures/addons/git/
assert refresh_cache_calls == {"local_ssh", "local_example", "core_samba"}
@@ -82,11 +77,11 @@ async def test_load_with_custom_repository(coresys: CoreSys):
):
await store_manager.load()
assert len(store_manager.all) == 6
assert len(store_manager.all) == 5
assert isinstance(store_manager.get("core"), Repository)
assert isinstance(store_manager.get("local"), Repository)
assert len(store_manager.repository_urls) == 4
assert len(store_manager.repository_urls) == 3
assert (
"https://github.com/hassio-addons/repository" in store_manager.repository_urls
)
@@ -94,10 +89,6 @@ async def test_load_with_custom_repository(coresys: CoreSys):
"https://github.com/esphome/home-assistant-addon"
in store_manager.repository_urls
)
assert (
"https://github.com/music-assistant/home-assistant-addon"
in store_manager.repository_urls
)
assert "http://example.com" in store_manager.repository_urls
@@ -114,11 +105,11 @@ async def test_load_from_core_config(coresys: CoreSys):
), patch("pathlib.Path.exists", return_value=True):
await coresys.store.load()
assert len(coresys.store.all) == 6
assert len(coresys.store.all) == 5
assert isinstance(coresys.store.get("core"), Repository)
assert isinstance(coresys.store.get("local"), Repository)
assert len(coresys.store.repository_urls) == 4
assert len(coresys.store.repository_urls) == 3
assert (
"https://github.com/hassio-addons/repository" in coresys.store.repository_urls
)
@@ -126,10 +117,6 @@ async def test_load_from_core_config(coresys: CoreSys):
"https://github.com/esphome/home-assistant-addon"
in coresys.store.repository_urls
)
assert (
"https://github.com/music-assistant/home-assistant-addon"
in coresys.store.repository_urls
)
assert "http://example.com" in coresys.store.repository_urls
assert coresys.config.addons_repositories == []
@@ -256,12 +243,12 @@ async def test_install_unavailable_addon(
async def test_reload(coresys: CoreSys):
"""Test store reload."""
await coresys.store.load()
assert len(coresys.store.all) == 5
assert len(coresys.store.all) == 4
with patch.object(GitRepo, "pull") as git_pull:
await coresys.store.reload()
assert git_pull.call_count == 4
assert git_pull.call_count == 3
async def test_addon_version_timestamp(coresys: CoreSys, install_addon_example: Addon):

View File

@@ -49,13 +49,12 @@ async def test_repository_validate(repo_list: list[str], valid: bool):
"""Test repository list validate."""
if valid:
processed = repositories(repo_list)
assert len(processed) == 5
assert len(processed) == 4
assert set(repositories(repo_list)) == {
"core",
"local",
"https://github.com/hassio-addons/repository",
"https://github.com/esphome/home-assistant-addon",
"https://github.com/music-assistant/home-assistant-addon",
}
else:
with pytest.raises(Invalid):