diff --git a/supervisor/addons/addon.py b/supervisor/addons/addon.py index b0a50503c..e2057a61c 100644 --- a/supervisor/addons/addon.py +++ b/supervisor/addons/addon.py @@ -926,6 +926,10 @@ class Addon(AddonModel): await self.sys_addons.data.update(store) await self._check_ingress_port() + # Reload ingress tokens in case addon gained ingress support + if self.with_ingress: + await self.sys_ingress.reload() + # Cleanup with suppress(DockerError): await self.instance.cleanup( @@ -979,6 +983,11 @@ class Addon(AddonModel): await self.sys_addons.data.update(self.addon_store) await self._check_ingress_port() + + # Reload ingress tokens in case addon gained ingress support + if self.with_ingress: + await self.sys_ingress.reload() + _LOGGER.info("Add-on '%s' successfully rebuilt", self.slug) finally: diff --git a/tests/addons/test_manager.py b/tests/addons/test_manager.py index 9973a9449..00bc38b99 100644 --- a/tests/addons/test_manager.py +++ b/tests/addons/test_manager.py @@ -13,7 +13,7 @@ import pytest from supervisor.addons.addon import Addon from supervisor.arch import CpuArchManager from supervisor.config import CoreConfig -from supervisor.const import AddonBoot, AddonStartup, AddonState, BusEvent +from supervisor.const import ATTR_INGRESS, AddonBoot, AddonStartup, AddonState, BusEvent from supervisor.coresys import CoreSys from supervisor.docker.addon import DockerAddon from supervisor.docker.const import ContainerState @@ -528,6 +528,75 @@ async def test_shared_image_kept_on_uninstall( assert not coresys.addons.get("local_example", local_only=True) +@pytest.mark.usefixtures("tmp_supervisor_data", "path_extern") +async def test_update_reloads_ingress_tokens( + coresys: CoreSys, + install_addon_ssh: Addon, + container: DockerContainer, +): + """Test ingress tokens are reloaded when addon gains ingress on update.""" + container.show.return_value["State"]["Status"] = "stopped" + container.show.return_value["State"]["Running"] = False + install_addon_ssh.path_data.mkdir() + + # Simulate addon was installed without ingress + coresys.addons.data.system[install_addon_ssh.slug][ATTR_INGRESS] = False + await install_addon_ssh.load() + await coresys.ingress.reload() + assert install_addon_ssh.ingress_token not in coresys.ingress.tokens + + # Update store to version with ingress enabled + with patch( + "supervisor.store.data.read_json_or_yaml_file", + return_value=load_json_fixture("addon-config-add-image.json"), + ): + await coresys.store.data.update() + + assert install_addon_ssh.need_update is True + + with ( + patch.object(DockerInterface, "install"), + patch.object(DockerAddon, "is_running", return_value=False), + ): + await coresys.addons.update(TEST_ADDON_SLUG) + + # Ingress token should now be registered + assert install_addon_ssh.with_ingress is True + assert install_addon_ssh.ingress_token in coresys.ingress.tokens + + +@pytest.mark.usefixtures("tmp_supervisor_data", "path_extern") +async def test_rebuild_reloads_ingress_tokens( + coresys: CoreSys, + install_addon_ssh: Addon, + container: DockerContainer, +): + """Test ingress tokens are reloaded when addon gains ingress on rebuild.""" + container.show.return_value["State"]["Status"] = "stopped" + container.show.return_value["State"]["Running"] = False + install_addon_ssh.path_data.mkdir() + + # Simulate addon was installed without ingress + coresys.addons.data.system[install_addon_ssh.slug][ATTR_INGRESS] = False + await install_addon_ssh.load() + await coresys.ingress.reload() + assert install_addon_ssh.ingress_token not in coresys.ingress.tokens + + # Re-enable ingress in system data (rebuild pulls fresh store data) + coresys.addons.data.system[install_addon_ssh.slug][ATTR_INGRESS] = True + + with ( + patch.object(DockerAddon, "_build"), + patch.object(DockerAddon, "is_running", return_value=False), + patch.object(Addon, "need_build", new=PropertyMock(return_value=True)), + ): + await coresys.addons.rebuild(TEST_ADDON_SLUG) + + # Ingress token should now be registered + assert install_addon_ssh.with_ingress is True + assert install_addon_ssh.ingress_token in coresys.ingress.tokens + + async def test_shared_image_kept_on_update( coresys: CoreSys, install_addon_example_image: Addon, docker: DockerAPI ):