async def test_approve(coresys: CoreSys, tmp_path): """Test check.""" with patch("supervisor.config.CoreConfig.path_homeassistant", tmp_path): core_security = CheckCoreSecurity(coresys) coresys.core.state = CoreState.RUNNING coresys.homeassistant._data["version"] = None assert await core_security.approve_check() coresys.homeassistant._data["version"] = AwesomeVersion("2021.1.3") assert not await core_security.approve_check() coresys.homeassistant._data["version"] = AwesomeVersion("2021.1.2") assert not await core_security.approve_check() Path(coresys.config.path_homeassistant, "custom_components").mkdir(parents=True) assert await core_security.approve_check()
def new(data: dict[str, Any]): """Create a object from docker info.""" return DockerInfo( AwesomeVersion(data.get("ServerVersion", "0.0.0")), data.get("Driver", "unknown"), data.get("LoggingDriver", "unknown"), data.get("CgroupVersion", "1"), )
def native_unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity.""" set_req = self.gateway.const.SetReq if (AwesomeVersion( self.gateway.protocol_version) >= AwesomeVersion("1.5") and set_req.V_UNIT_PREFIX in self._values): custom_unit: str = self._values[set_req.V_UNIT_PREFIX] return custom_unit if set_req(self.value_type) == set_req.V_TEMP: if self.hass.config.units.is_metric: return TEMP_CELSIUS return TEMP_FAHRENHEIT if hasattr(self, "entity_description"): return self.entity_description.native_unit_of_measurement return None
def release_url(self) -> str | None: """URL to the full release notes of the latest version available.""" version = AwesomeVersion(self.latest_version) if version.dev or version.strategy == AwesomeVersionStrategy.UNKNOWN: return "https://github.com/home-assistant/operating-system/commits/dev" return ( f"https://github.com/home-assistant/operating-system/releases/tag/{version}" )
def resolve_from_root(cls, hass: HomeAssistant, root_module: ModuleType, domain: str) -> Integration | None: """Resolve an integration from a root module.""" for base in root_module.__path__: manifest_path = pathlib.Path(base) / domain / "manifest.json" if not manifest_path.is_file(): continue try: manifest = json.loads(manifest_path.read_text()) except ValueError as err: _LOGGER.error("Error parsing manifest.json file at %s: %s", manifest_path, err) continue integration = cls( hass, f"{root_module.__name__}.{domain}", manifest_path.parent, manifest, ) if integration.is_built_in: return integration _LOGGER.warning(CUSTOM_WARNING, integration.domain) if integration.version is None: _LOGGER.error( "The custom integration '%s' does not have a " "version key in the manifest file and was blocked from loading. " "See https://developers.home-assistant.io/blog/2021/01/29/custom-integration-changes#versions for more details", integration.domain, ) return None try: AwesomeVersion( integration.version, [ AwesomeVersionStrategy.CALVER, AwesomeVersionStrategy.SEMVER, AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.BUILDVER, AwesomeVersionStrategy.PEP440, ], ) except AwesomeVersionException: _LOGGER.error( "The custom integration '%s' does not have a " "valid version key (%s) in the manifest file and was blocked from loading. " "See https://developers.home-assistant.io/blog/2021/01/29/custom-integration-changes#versions for more details", integration.domain, integration.version, ) return None return integration return None
def version_tag( value: Union[str, None, int, float, AwesomeVersion] ) -> Optional[AwesomeVersion]: """Validate main version handling.""" if value is None: return None if isinstance(value, AwesomeVersion): return value return AwesomeVersion(value)
def validate_custom_integration_version(version: str) -> bool: """Validate the version of custom integrations.""" return AwesomeVersion(version).strategy in ( AwesomeVersionStrategy.CALVER, AwesomeVersionStrategy.SEMVER, AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.BUILDVER, AwesomeVersionStrategy.PEP440, )
def test_version_scheme(version, strategy, dev, beta, modifier, modifier_type): """Test that the version matches the expected scheme.""" version_object = AwesomeVersion(version) assert str(version_object) == version assert version_object.strategy == strategy assert version_object.dev == dev assert version_object.beta == beta assert version_object.modifier == modifier assert version_object.modifier_type == modifier_type
async def test_docker_image_platform(coresys: CoreSys, cpu_arch: str, platform: str): """Test platform set correctly from arch.""" with patch.object(coresys.docker.images, "pull", return_value=Mock(id="test:1.2.3")) as pull: instance = DockerInterface(coresys) await instance.install(AwesomeVersion("1.2.3"), "test", arch=cpu_arch) assert pull.call_count == 1 assert pull.call_args == call("test:1.2.3", platform=platform)
def test_modifier(): handler = CompareHandlers(AwesomeVersion("1.0b1"), AwesomeVersion("1.0b0")) assert handler.check() handler = CompareHandlers(AwesomeVersion("1.0"), AwesomeVersion("1.0b0")) assert handler.check() handler = CompareHandlers(AwesomeVersion("1.0b0"), AwesomeVersion("1.0b1")) assert not handler.check() handler = CompareHandlers(AwesomeVersion("1.0b0"), AwesomeVersion("1.0")) assert not handler.check()
def get_mac_addr(self): """Increment the last byte of the mac address by one for FW>3.70.""" if (self.bulb.host_firmware_version and AwesomeVersion(self.bulb.host_firmware_version) >= FIX_MAC_FW): octets = [ int(octet, 16) for octet in self.bulb.mac_addr.split(":") ] octets[5] = (octets[5] + 1) % 256 return ":".join(f"{octet:02x}" for octet in octets) return self.bulb.mac_addr
def is_ha_supported() -> bool: """Return True, if current HA version is supported.""" if AwesomeVersion(HA_VERSION) >= MIN_REQUIRED_HA_VERSION: return True _LOGGER.error( 'Unsupported HA version! Please upgrade home assistant at least to "%s"', MIN_REQUIRED_HA_VERSION, ) return False
def _extract_version_from_server_response(server_response): """Attempt to extract version from server response.""" try: return AwesomeVersion( server_response, ensure_strategy=AwesomeVersionStrategy.SIMPLEVER, find_first_match=True, ) except AwesomeVersionException: return None
async def coresys(loop, docker, network_manager, aiohttp_client, run_dir) -> CoreSys: """Create a CoreSys Mock.""" with patch("supervisor.bootstrap.initialize_system"), patch( "supervisor.bootstrap.setup_diagnostics"): coresys_obj = await initialize_coresys() # Mock save json coresys_obj._ingress.save_data = MagicMock() coresys_obj._auth.save_data = MagicMock() coresys_obj._updater.save_data = MagicMock() coresys_obj._config.save_data = MagicMock() coresys_obj._jobs.save_data = MagicMock() coresys_obj._resolution.save_data = MagicMock() coresys_obj._addons.data.save_data = MagicMock() coresys_obj._store.save_data = MagicMock() # Mock test client coresys_obj.arch._default_arch = "amd64" coresys_obj._machine = "qemux86-64" coresys_obj._machine_id = uuid4() # Mock host communication coresys_obj._dbus._network = network_manager # Mock docker coresys_obj._docker = docker # Set internet state coresys_obj.supervisor._connectivity = True coresys_obj.host.network._connectivity = True # Fix Paths su_config.ADDONS_CORE = Path( Path(__file__).parent.joinpath("fixtures"), "addons/core") su_config.ADDONS_LOCAL = Path( Path(__file__).parent.joinpath("fixtures"), "addons/local") su_config.ADDONS_GIT = Path( Path(__file__).parent.joinpath("fixtures"), "addons/git") # WebSocket coresys_obj.homeassistant.api.check_api_state = mock_async_return_true coresys_obj.homeassistant._websocket._client = AsyncMock( ha_version=AwesomeVersion("2021.2.4")) # Remove rate limiting decorator from fetch_data coresys_obj.updater.fetch_data = partial( unwrap(coresys_obj.updater.fetch_data), coresys_obj.updater) # Don't remove files/folders related to addons and stores with patch("supervisor.store.git.GitRepo._remove"): yield coresys_obj await coresys_obj.websession.close()
async def approve_check(self, reference: Optional[str] = None) -> bool: """Approve check if it is affected by issue.""" try: if self.sys_homeassistant.version >= AwesomeVersion("2021.1.5"): return False except AwesomeVersionException: return True if not Path(self.sys_config.path_homeassistant, "custom_components").exists(): return False return True
def pending_update(self) -> bool: if not self.can_install: return False if self.data.installed: if self.data.selected_tag is not None: if self.data.selected_tag == self.data.default_branch: if self.data.installed_commit != self.data.last_commit: return True return False if self.display_version_or_commit == "version": try: return AwesomeVersion(self.display_available_version) > AwesomeVersion( self.display_installed_version ) except AwesomeVersionException: pass if self.display_installed_version != self.display_available_version: return True return False
async def test_docker_image_default_platform(coresys: CoreSys): """Test platform set using supervisor arch when omitted.""" with patch.object(type(coresys.supervisor), "arch", PropertyMock(return_value="i386")), patch.object( coresys.docker.images, "pull", return_value=Mock(id="test:1.2.3")) as pull: instance = DockerInterface(coresys) await instance.install(AwesomeVersion("1.2.3"), "test") assert pull.call_count == 1 assert pull.call_args == call("test:1.2.3", platform="linux/386")
async def _async_update_data(self) -> dict[str, IntegrationAlert]: response = await async_get_clientsession(self.hass).get( "https://alerts.home-assistant.io/alerts.json", timeout=aiohttp.ClientTimeout(total=10), ) alerts = await response.json() result = {} for alert in alerts: if "integrations" not in alert: continue if "homeassistant" in alert: if "affected_from_version" in alert["homeassistant"]: affected_from_version = AwesomeVersion( alert["homeassistant"]["affected_from_version"], ) if self.ha_version < affected_from_version: continue if "resolved_in_version" in alert["homeassistant"]: resolved_in_version = AwesomeVersion( alert["homeassistant"]["resolved_in_version"], ) if self.ha_version >= resolved_in_version: continue for integration in alert["integrations"]: if "package" not in integration: continue if integration["package"] not in self.hass.config.components: continue integration_alert = IntegrationAlert( integration=integration["package"], filename=alert["filename"], date_updated=alert.get("date_updated"), ) result[integration_alert.issue_id] = integration_alert return result
def __init__(self, hass: HomeAssistant) -> None: """Initialize the data updater.""" super().__init__( hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL, ) self.ha_version = AwesomeVersion( __version__, ensure_strategy=AwesomeVersionStrategy.CALVER, )
async def check_new_version() -> Updater: """Check if a new version is available and report if one is.""" newest, release_notes = await get_newest_version( hass, huuid, include_components) _LOGGER.debug("Fetched version %s: %s", newest, release_notes) # Skip on dev if "dev" in current_version: return Updater(False, "", "") # Load data from Supervisor if hass.components.hassio.is_hassio(): core_info = hass.components.hassio.get_core_info() newest = core_info["version_latest"] # Validate version update_available = False if AwesomeVersion(newest) > AwesomeVersion(current_version): _LOGGER.debug( "The latest available version of Home Assistant is %s", newest) update_available = True elif AwesomeVersion(newest) == AwesomeVersion(current_version): _LOGGER.debug( "You are on the latest version (%s) of Home Assistant", newest) elif AwesomeVersion(newest) < AwesomeVersion(current_version): _LOGGER.debug( "Local version (%s) is newer than the latest available version (%s)", current_version, newest, ) _LOGGER.debug("Update available: %s", update_available) return Updater(update_available, newest, release_notes)
async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. """ api = PrusaLink(async_get_clientsession(hass), data["host"], data["api_key"]) try: async with async_timeout.timeout(5): version = await api.get_version() except (asyncio.TimeoutError, ClientError) as err: _LOGGER.error("Could not connect to PrusaLink: %s", err) raise CannotConnect from err try: if AwesomeVersion(version["api"]) < AwesomeVersion("2.0.0"): raise NotSupported except AwesomeVersionException as err: raise NotSupported from err return {"title": version["hostname"]}
def _validate_version(version: str) -> dict[str, str]: """Validate a version string from the user.""" version_okay = True try: AwesomeVersion( version, [AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.SEMVER], ) except AwesomeVersionStrategyException: version_okay = False if version_okay: return {} return {CONF_VERSION: "invalid_version"}
def verify_version(value: str): """Verify the version.""" version = AwesomeVersion(value) if version.strategy not in [ AwesomeVersionStrategy.CALVER, AwesomeVersionStrategy.SEMVER, AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.BUILDVER, AwesomeVersionStrategy.PEP440, ]: raise vol.Invalid( f"'{version}' is not a valid version. This will cause a future version of Home Assistant to block this integration.", ) return value
def test_defaults(coresys): """Test event defaults.""" coresys.config.diagnostics = True coresys.core.state = CoreState.RUNNING with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))): filtered = filter_data(coresys, SAMPLE_EVENT, {}) assert ["installation_type", "supervised"] in filtered["tags"] assert filtered["contexts"]["host"]["arch"] == "amd64" assert filtered["contexts"]["host"]["machine"] == "qemux86-64" assert filtered["contexts"]["versions"]["supervisor"] == AwesomeVersion( SUPERVISOR_VERSION) assert filtered["user"]["id"] == coresys.machine_id
def _validate_version(version: str) -> dict[str, str]: """Validate a version string from the user.""" version_okay = False with suppress(AwesomeVersionStrategyException): version_okay = bool( AwesomeVersion.ensure_strategy( version, [AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.SEMVER], ) ) if version_okay: return {} return {CONF_VERSION: "invalid_version"}
async def run_check(self) -> None: """Run check if not affected by issue.""" try: if self.sys_homeassistant.version < AwesomeVersion("2021.1.5"): if Path(self.sys_config.path_homeassistant, "custom_components").exists(): self.sys_resolution.create_issue( IssueType.SECURITY, ContextType.CORE, reference=SecurityReference. CUSTOM_COMPONENTS_BELOW_2021_1_5, suggestions=[SuggestionType.EXECUTE_UPDATE], ) except (AwesomeVersionException, OSError): return
def verify_version(value: str): """Verify the version.""" try: AwesomeVersion( value, [ AwesomeVersionStrategy.CALVER, AwesomeVersionStrategy.SEMVER, AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.BUILDVER, AwesomeVersionStrategy.PEP440, ], ) except AwesomeVersionException: raise vol.Invalid(f"'{value}' is not a valid version.") return value
def test_container(): handler = CompareHandlers(AwesomeVersion("latest"), AwesomeVersion("stable")) assert handler.check() handler = CompareHandlers(AwesomeVersion("latest"), AwesomeVersion("1")) assert handler.check() handler = CompareHandlers(AwesomeVersion("1.0.0"), AwesomeVersion("stable")) assert not handler.check()
def device_info(self): """Return device information about NetDaemon.""" info = { "identifiers": {(DOMAIN, ND_ID)}, "name": NAME, "sw_version": INTEGRATION_VERSION, "manufacturer": "netdaemon.xyz", } # LEGACY can be removed when min HA version is 2021.12 if AwesomeVersion(HA_VERSION) >= "2021.12.0b0": # pylint: disable=import-outside-toplevel from homeassistant.helpers.device_registry import DeviceEntryType info["entry_type"] = DeviceEntryType.SERVICE else: info["entry_type"] = "service" return info
def parse(self): """Logic to parse new version data.""" self._version = self.data.get(DATA_INFO, {}).get(DATA_VERSION) versions = sorted( [ version for version in self.data.get(DATA_RELEASES, []) if version.startswith("2") ], reverse=True, ) for version in versions: version = AwesomeVersion(version) if self.channel == HaVersionChannel.STABLE and version.beta: continue self._version = version break