Merge pull request #157213 from mweinelt/hass-module

This commit is contained in:
Martin Weinelt 2022-02-16 00:40:22 +01:00 committed by GitHub
commit e43b87552d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 375 additions and 149 deletions

View file

@ -269,6 +269,23 @@
<literal>(ghc.withPackages.override { useLLVM = true; }) (p: [])</literal>.
</para>
</listitem>
<listitem>
<para>
The <literal>home-assistant</literal> module now requires
users that dont want their configuration to be managed
declaratively to set
<literal>services.home-assistant.config = null;</literal>.
This is required due to the way default settings are handled
with the new settings style.
</para>
<para>
Additionally the default list of
<literal>extraComponents</literal> now includes the minimal
dependencies to successfully complete the
<link xlink:href="https://www.home-assistant.io/getting-started/onboarding/">onboarding</link>
procedure.
</para>
</listitem>
<listitem>
<para>
<literal>pkgs.emacsPackages.orgPackages</literal> is removed

View file

@ -91,6 +91,15 @@ In addition to numerous new and upgraded packages, this release has the followin
`useLLVM`. So instead of `(ghc.withPackages (p: [])).override { withLLVM = true; }`,
one needs to use `(ghc.withPackages.override { useLLVM = true; }) (p: [])`.
- The `home-assistant` module now requires users that don't want their
configuration to be managed declaratively to set
`services.home-assistant.config = null;`. This is required
due to the way default settings are handled with the new settings style.
Additionally the default list of `extraComponents` now includes the minimal
dependencies to successfully complete the [onboarding](https://www.home-assistant.io/getting-started/onboarding/)
procedure.
- `pkgs.emacsPackages.orgPackages` is removed because org elpa is deprecated.
The packages in the top level of `pkgs.emacsPackages`, such as org and
org-contrib, refer to the ones in `pkgs.emacsPackages.elpaPackages` and

View file

@ -450,6 +450,7 @@
./services/hardware/undervolt.nix
./services/hardware/vdr.nix
./services/hardware/xow.nix
./services/home-automation/home-assistant.nix
./services/logging/SystemdJournal2Gelf.nix
./services/logging/awstats.nix
./services/logging/filebeat.nix
@ -545,7 +546,6 @@
./services/misc/headphones.nix
./services/misc/heisenbridge.nix
./services/misc/greenclip.nix
./services/misc/home-assistant.nix
./services/misc/ihaskell.nix
./services/misc/input-remapper.nix
./services/misc/irkerd.nix

View file

@ -4,35 +4,27 @@ with lib;
let
cfg = config.services.home-assistant;
format = pkgs.formats.yaml {};
# cfg.config != null can be assumed here
configJSON = pkgs.writeText "configuration.json"
(builtins.toJSON (if cfg.applyDefaultConfig then
(recursiveUpdate defaultConfig cfg.config) else cfg.config));
# Render config attribute sets to YAML
# Values that are null will be filtered from the output, so this is one way to have optional
# options shown in settings.
# We post-process the result to add support for YAML functions, like secrets or includes, see e.g.
# https://www.home-assistant.io/docs/configuration/secrets/
filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) cfg.config or {};
configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
# Hack to support custom yaml objects,
# i.e. secrets: https://www.home-assistant.io/docs/configuration/secrets/
cp ${format.generate "configuration.yaml" filteredConfig} $out
sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
'';
lovelaceConfig = cfg.lovelaceConfig or {};
lovelaceConfigFile = format.generate "ui-lovelace.yaml" lovelaceConfig;
lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json"
(builtins.toJSON cfg.lovelaceConfig);
lovelaceConfigFile = pkgs.runCommand "ui-lovelace.yaml" { preferLocalBuild = true; } ''
${pkgs.remarshal}/bin/json2yaml -i ${lovelaceConfigJSON} -o $out
'';
# Components advertised by the home-assistant package
availableComponents = cfg.package.availableComponents;
# Components that were added by overriding the package
explicitComponents = cfg.package.extraComponents;
usedPlatforms = config:
if isAttrs config then
optional (config ? platform) config.platform
++ concatMap usedPlatforms (attrValues config)
else if isList config then
concatMap usedPlatforms config
else [ ];
useExplicitComponent = component: elem component explicitComponents;
# Given a component "platform", looks up whether it is used in the config
# as `platform = "platform";`.
@ -42,33 +34,45 @@ let
# platform = "mqtt";
# ...
# } ];
usedPlatforms = config:
if isAttrs config then
optional (config ? platform) config.platform
++ concatMap usedPlatforms (attrValues config)
else if isList config then
concatMap usedPlatforms config
else [ ];
useComponentPlatform = component: elem component (usedPlatforms cfg.config);
useExplicitComponent = component: elem component explicitComponents;
# Returns whether component is used in config or explicitly passed into package
# Returns whether component is used in config, explicitly passed into package or
# configured in the module.
useComponent = component:
hasAttrByPath (splitString "." component) cfg.config
|| useComponentPlatform component
|| useExplicitComponent component;
|| useExplicitComponent component
|| builtins.elem component cfg.extraComponents;
# List of components used in config
# Final list of components passed into the package to include required dependencies
extraComponents = filter useComponent availableComponents;
package = if (cfg.autoExtraComponents && cfg.config != null)
then (cfg.package.override { inherit extraComponents; })
else cfg.package;
# If you are changing this, please update the description in applyDefaultConfig
defaultConfig = {
homeassistant.time_zone = config.time.timeZone;
http.server_port = cfg.port;
} // optionalAttrs (cfg.lovelaceConfig != null) {
lovelace.mode = "yaml";
};
package = (cfg.package.override (oldArgs: {
# Respect overrides that already exist in the passed package and
# concat it with values passed via the module.
extraComponents = oldArgs.extraComponents ++ extraComponents;
extraPackages = ps: (oldArgs.extraPackages ps) ++ (cfg.extraPackages ps);
}));
in {
meta.maintainers = teams.home-assistant.members;
imports = [
# Migrations in NixOS 22.05
(mkRemovedOptionModule [ "services" "home-assistant" "applyDefaultConfig" ] "The default config was migrated into services.home-assistant.config")
(mkRemovedOptionModule [ "services" "home-assistant" "autoExtraComponents" ] "Components are now parsed from services.home-assistant.config unconditionally")
(mkRenamedOptionModule [ "services" "home-assistant" "port" ] [ "services" "home-assistant" "config" "http" "server_port" ])
];
meta = {
buildDocsInSandbox = false;
maintainers = teams.home-assistant.members;
};
options.services.home-assistant = {
# Running home-assistant on NixOS is considered an installation method that is unsupported by the upstream project.
@ -81,42 +85,166 @@ in {
description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
};
port = mkOption {
default = 8123;
type = types.port;
description = "The port on which to listen.";
extraComponents = mkOption {
type = types.listOf (types.enum availableComponents);
default = [
# List of components required to complete the onboarding
"default_config"
"met"
"esphome"
] ++ optionals (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) [
# Use the platform as an indicator that we might be running on a RaspberryPi and include
# relevant components
"rpi_power"
];
example = literalExpression ''
[
"analytics"
"default_config"
"esphome"
"my"
"shopping_list"
"wled"
]
'';
description = ''
List of <link xlink:href="https://www.home-assistant.io/integrations/">components</link> that have their dependencies included in the package.
The component name can be found in the URL, for example <literal>https://www.home-assistant.io/integrations/ffmpeg/</literal> would map to <literal>ffmpeg</literal>.
'';
};
applyDefaultConfig = mkOption {
default = true;
type = types.bool;
extraPackages = mkOption {
type = types.functionTo (types.listOf types.package);
default = _: [];
defaultText = literalExpression ''
python3Packages: with python3Packages; [];
'';
example = literalExpression ''
python3Packages: with python3Packages; [
# postgresql support
psycopg2
];
'';
description = ''
Setting this option enables a few configuration options for HA based on NixOS configuration (such as time zone) to avoid having to manually specify configuration we already have.
</para>
<para>
Currently one side effect of enabling this is that the <literal>http</literal> component will be enabled.
</para>
<para>
This only takes effect if <literal>config != null</literal> in order to ensure that a manually managed <filename>configuration.yaml</filename> is not overwritten.
List of packages to add to propagatedBuildInputs.
A popular example is <package>python3Packages.psycopg2</package>
for PostgreSQL support in the recorder component.
'';
};
config = mkOption {
default = null;
# Migrate to new option types later: https://github.com/NixOS/nixpkgs/pull/75584
type = with lib.types; let
valueType = nullOr (oneOf [
bool
int
float
str
(lazyAttrsOf valueType)
(listOf valueType)
]) // {
description = "Yaml value";
emptyValue.value = {};
type = types.submodule {
freeformType = format.type;
options = {
# This is a partial selection of the most common options, so new users can quickly
# pick up how to match home-assistants config structure to ours. It also lets us preset
# config values intelligently.
homeassistant = {
# https://www.home-assistant.io/docs/configuration/basic/
name = mkOption {
type = types.nullOr types.str;
default = null;
example = "Home";
description = ''
Name of the location where Home Assistant is running.
'';
};
latitude = mkOption {
type = types.nullOr (types.either types.float types.str);
default = null;
example = 52.3;
description = ''
Latitude of your location required to calculate the time the sun rises and sets.
'';
};
longitude = mkOption {
type = types.nullOr (types.either types.float types.str);
default = null;
example = 4.9;
description = ''
Longitude of your location required to calculate the time the sun rises and sets.
'';
};
unit_system = mkOption {
type = types.nullOr (types.enum [ "metric" "imperial" ]);
default = null;
example = "metric";
description = ''
The unit system to use. This also sets temperature_unit, Celsius for Metric and Fahrenheit for Imperial.
'';
};
temperature_unit = mkOption {
type = types.nullOr (types.enum [ "C" "F" ]);
default = null;
example = "C";
description = ''
Override temperature unit set by unit_system. <literal>C</literal> for Celsius, <literal>F</literal> for Fahrenheit.
'';
};
time_zone = mkOption {
type = types.nullOr types.str;
default = config.time.timeZone or null;
defaultText = literalExpression ''
config.time.timeZone or null
'';
example = "Europe/Amsterdam";
description = ''
Pick your time zone from the column TZ of Wikipedias <link xlink:href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">list of tz database time zones</link>.
'';
};
};
in valueType;
http = {
# https://www.home-assistant.io/integrations/http/
server_host = mkOption {
type = types.either types.str (types.listOf types.str);
default = [
"0.0.0.0"
"::"
];
example = "::1";
description = ''
Only listen to incoming requests on specific IP/host. The default listed assumes support for IPv4 and IPv6.
'';
};
server_port = mkOption {
default = 8123;
type = types.port;
description = ''
The port on which to listen.
'';
};
};
lovelace = {
# https://www.home-assistant.io/lovelace/dashboards/
mode = mkOption {
type = types.enum [ "yaml" "storage" ];
default = if cfg.lovelaceConfig != null
then "yaml"
else "storage";
defaultText = literalExpression ''
if cfg.lovelaceConfig != null
then "yaml"
else "storage";
'';
example = "yaml";
description = ''
In what mode should the main Lovelace panel be, <literal>yaml</literal> or <literal>storage</literal> (UI managed).
'';
};
};
};
};
example = literalExpression ''
{
homeassistant = {
@ -130,15 +258,19 @@ in {
frontend = {
themes = "!include_dir_merge_named themes";
};
http = { };
http = {};
feedreader.urls = [ "https://nixos.org/blogs.xml" ];
}
'';
description = ''
Your <filename>configuration.yaml</filename> as a Nix attribute set.
Beware that setting this option will delete your previous <filename>configuration.yaml</filename>.
<link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">Secrets</link>
are encoded as strings as shown in the example.
YAML functions like <link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">secrets</link>
can be passed as a string and will be unquoted automatically.
Unless this option is explicitly set to <literal>null</literal>
we assume your <filename>configuration.yaml</filename> is
managed through this module and thereby overwritten on startup.
'';
};
@ -147,16 +279,18 @@ in {
type = types.bool;
description = ''
Whether to make <filename>configuration.yaml</filename> writable.
This only has an effect if <option>config</option> is set.
This will allow you to edit it from Home Assistant's web interface.
This only has an effect if <option>config</option> is set.
However, bear in mind that it will be overwritten at every start of the service.
'';
};
lovelaceConfig = mkOption {
default = null;
type = with types; nullOr attrs;
# from https://www.home-assistant.io/lovelace/yaml-mode/
type = types.nullOr format.type;
# from https://www.home-assistant.io/lovelace/dashboards/
example = literalExpression ''
{
title = "My Awesome Home";
@ -172,8 +306,8 @@ in {
'';
description = ''
Your <filename>ui-lovelace.yaml</filename> as a Nix attribute set.
Setting this option will automatically add
<literal>lovelace.mode = "yaml";</literal> to your <option>config</option>.
Setting this option will automatically set <literal>lovelace.mode</literal> to <literal>yaml</literal>.
Beware that setting this option will delete your previous <filename>ui-lovelace.yaml</filename>
'';
};
@ -183,8 +317,10 @@ in {
type = types.bool;
description = ''
Whether to make <filename>ui-lovelace.yaml</filename> writable.
This only has an effect if <option>lovelaceConfig</option> is set.
This will allow you to edit it from Home Assistant's web interface.
This only has an effect if <option>lovelaceConfig</option> is set.
However, bear in mind that it will be overwritten at every start of the service.
'';
};
@ -201,11 +337,18 @@ in {
type = types.package;
example = literalExpression ''
pkgs.home-assistant.override {
extraPackages = ps: with ps; [ colorlog ];
extraPackages = python3Packages: with python3Packages; [
psycopg2
];
extraComponents = [
"default_config"
"esphome"
"met"
];
}
'';
description = ''
Home Assistant package to use. By default the tests are disabled, as they take a considerable amout of time to complete.
The Home Assistant package to use.
Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies.
If you specify <option>config</option> and do not set <option>autoExtraComponents</option>
to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect.
@ -213,21 +356,6 @@ in {
'';
};
autoExtraComponents = mkOption {
default = true;
type = types.bool;
description = ''
If set to <literal>true</literal>, the components used in <literal>config</literal>
are set as the specified package's <literal>extraComponents</literal>.
This in turn adds all packaged dependencies to the derivation.
You might still see import errors in your log.
In this case, you will need to package the necessary dependencies yourself
or ask for someone else to package them.
If a dependency is packaged but not automatically added to this list,
you might need to specify it in <literal>extraPackages</literal>.
'';
};
openFirewall = mkOption {
default = false;
type = types.bool;
@ -240,18 +368,30 @@ in {
systemd.services.home-assistant = {
description = "Home Assistant";
after = [ "network.target" ];
preStart = optionalString (cfg.config != null) (if cfg.configWritable then ''
cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml"
'' else ''
rm -f "${cfg.configDir}/configuration.yaml"
ln -s ${configFile} "${cfg.configDir}/configuration.yaml"
'') + optionalString (cfg.lovelaceConfig != null) (if cfg.lovelaceConfigWritable then ''
cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
'' else ''
rm -f "${cfg.configDir}/ui-lovelace.yaml"
ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
'');
after = [
"network-online.target"
# prevent races with database creation
"mysql.service"
"postgresql.service"
];
preStart = let
copyConfig = if cfg.configWritable then ''
cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml"
'' else ''
rm -f "${cfg.configDir}/configuration.yaml"
ln -s ${configFile} "${cfg.configDir}/configuration.yaml"
'';
copyLovelaceConfig = if cfg.lovelaceConfigWritable then ''
cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
'' else ''
rm -f "${cfg.configDir}/ui-lovelace.yaml"
ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
'';
in
(optionalString (cfg.config != null) copyConfig) +
(optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig)
;
serviceConfig = let
# List of capabilities to equip home-assistant with, depending on configured components
capabilities = [

View file

@ -10,6 +10,7 @@ in {
nodes.hass = { pkgs, ... }: {
environment.systemPackages = with pkgs; [ mosquitto ];
services.mosquitto = {
enable = true;
listeners = [ {
@ -21,14 +22,42 @@ in {
};
} ];
};
services.home-assistant = {
inherit configDir;
services.postgresql = {
enable = true;
ensureDatabases = [ "hass" ];
ensureUsers = [{
name = "hass";
ensurePermissions = {
"DATABASE hass" = "ALL PRIVILEGES";
};
}];
};
services.home-assistant = {
enable = true;
inherit configDir;
# tests loading components by overriding the package
package = (pkgs.home-assistant.override {
extraPackages = ps: with ps; [
colorama
];
extraComponents = [ "zha" ];
}).overrideAttrs (oldAttrs: {
doInstallCheck = false;
});
# tests loading components from the module
extraComponents = [
"wake_on_lan"
];
# test extra package passing from the module
extraPackages = python3Packages: with python3Packages; [
psycopg2
];
config = {
homeassistant = {
name = "Home";
@ -37,34 +66,58 @@ in {
longitude = "0.0";
elevation = 0;
};
# configure the recorder component to use the postgresql db
recorder.db_url = "postgresql://@/hass";
# we can't load default_config, because the updater requires
# network access and would cause an error, so load frontend
# here explicitly.
# https://www.home-assistant.io/integrations/frontend/
frontend = {};
# configure an mqtt broker connection
# https://www.home-assistant.io/integrations/mqtt
mqtt = {
broker = "127.0.0.1";
username = mqttUsername;
password = mqttPassword;
};
binary_sensor = [{
# create a mqtt sensor that syncs state with its mqtt topic
# https://www.home-assistant.io/integrations/sensor.mqtt/
binary_sensor = [ {
platform = "mqtt";
state_topic = "home-assistant/test";
payload_on = "let_there_be_light";
payload_off = "off";
}];
wake_on_lan = {};
switch = [{
} ];
# set up a wake-on-lan switch to test capset capability required
# for the ping suid wrapper
# https://www.home-assistant.io/integrations/wake_on_lan/
switch = [ {
platform = "wake_on_lan";
mac = "00:11:22:33:44:55";
host = "127.0.0.1";
}];
# tests component-based capability assignment (CAP_NET_BIND_SERVICE)
} ];
# test component-based capability assignment (CAP_NET_BIND_SERVICE)
# https://www.home-assistant.io/integrations/emulated_hue/
emulated_hue = {
host_ip = "127.0.0.1";
listen_port = 80;
};
# show mqtt interaction in the log
# https://www.home-assistant.io/integrations/logger/
logger = {
default = "info";
logs."homeassistant.components.mqtt" = "debug";
};
};
# configure the sample lovelace dashboard
lovelaceConfig = {
title = "My Awesome Home";
views = [{
@ -81,34 +134,57 @@ in {
};
testScript = ''
import re
start_all()
# Parse the package path out of the systemd unit, as we cannot
# access the final package, that is overriden inside the module,
# by any other means.
pattern = re.compile(r"path=(?P<path>[\/a-z0-9-.]+)\/bin\/hass")
response = hass.execute("systemctl show -p ExecStart home-assistant.service")[1]
match = pattern.search(response)
package = match.group('path')
hass.wait_for_unit("home-assistant.service")
with subtest("Check that YAML configuration file is in place"):
hass.succeed("test -L ${configDir}/configuration.yaml")
with subtest("lovelace config is copied because lovelaceConfigWritable = true"):
with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"):
hass.succeed("test -f ${configDir}/ui-lovelace.yaml")
with subtest("Check extraComponents and extraPackages are considered from the package"):
hass.succeed(f"grep -q 'colorama' {package}/extra_packages")
hass.succeed(f"grep -q 'zha' {package}/extra_components")
with subtest("Check extraComponents and extraPackages are considered from the module"):
hass.succeed(f"grep -q 'psycopg2' {package}/extra_packages")
hass.succeed(f"grep -q 'wake_on_lan' {package}/extra_components")
with subtest("Check that Home Assistant's web interface and API can be reached"):
hass.wait_until_succeeds("journalctl -u home-assistant.service | grep -q 'Home Assistant initialized in'")
hass.wait_for_open_port(8123)
hass.succeed("curl --fail http://localhost:8123/lovelace")
with subtest("Toggle a binary sensor using MQTT"):
hass.wait_for_open_port(1883)
hass.succeed(
"mosquitto_pub -V mqttv5 -t home-assistant/test -u ${mqttUsername} -P '${mqttPassword}' -m let_there_be_light"
)
with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"):
hass.wait_for_open_port(80)
hass.succeed("curl --fail http://localhost:80/description.xml")
with subtest("Check extra components are considered in systemd unit hardening"):
hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB")
with subtest("Print log to ease debugging"):
output_log = hass.succeed("cat ${configDir}/home-assistant.log")
print("\n### home-assistant.log ###\n")
print(output_log + "\n")
# wait for home-assistant to fully boot
hass.sleep(30)
hass.wait_for_unit("home-assistant.service")
with subtest("Check that no errors were logged"):
assert "ERROR" not in output_log
@ -117,7 +193,7 @@ in {
assert "let_there_be_light" in output_log
with subtest("Check systemd unit hardening"):
hass.log(hass.succeed("systemctl show home-assistant.service"))
hass.log(hass.succeed("systemctl cat home-assistant.service"))
hass.log(hass.succeed("systemd-analyze security home-assistant.service"))
'';
})

View file

@ -15,6 +15,9 @@
# Additional packages to add to propagatedBuildInputs
, extraPackages ? ps: []
# Write out info about included extraComponents and extraPackages
, writeText
# Override Python packages using
# self: super: { pkg = super.pkg.overridePythonAttrs (oldAttrs: { ... }); }
# Applied after defaultOverrides
@ -130,6 +133,10 @@ let
# Ensure that we are using a consistent package set
extraBuildInputs = extraPackages python.pkgs;
# Create info about included packages and components
extraComponentsFile = writeText "home-assistant-components" (lib.concatStringsSep "\n" extraComponents);
extraPackagesFile = writeText "home-assistant-packages" (lib.concatMapStringsSep "\n" (pkg: pkg.pname) extraBuildInputs);
# Don't forget to run parse-requirements.py after updating
hassVersion = "2022.2.6";
@ -158,7 +165,6 @@ in python.pkgs.buildPythonApplication rec {
src = ./patches/ffmpeg-path.patch;
ffmpeg = "${lib.getBin ffmpeg}/bin/ffmpeg";
})
./patches/tests-ignore-OSErrors-in-hass-fixture.patch
];
postPatch = let
@ -284,6 +290,11 @@ in python.pkgs.buildPythonApplication rec {
export PATH=${inetutils}/bin:$PATH
'';
postInstall = ''
cp -v ${extraComponentsFile} $out/extra_components
cp -v ${extraPackagesFile} $out/extra_packages
'';
passthru = {
inherit
availableComponents

View file

@ -1,27 +0,0 @@
From 3e3f5c37252a33ea1e71c39f2ca0f13940c261ad Mon Sep 17 00:00:00 2001
From: Martin Weinelt <hexa@darmstadt.ccc.de>
Date: Sat, 17 Jul 2021 16:11:23 +0200
Subject: [PATCH] tests: ignore OSErrors in hass fixture
The nix sandbox will cause OSErrors due to limitations imposed on
network interaction. This change makes it so we forgive these cases.
---
tests/conftest.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tests/conftest.py b/tests/conftest.py
index 1f5ffc80d0..b284727a0f 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -168,6 +168,8 @@ def hass(loop, load_registries, hass_storage, request):
continue
if isinstance(ex, ServiceNotFound):
continue
+ if isinstance(ex, OSError):
+ continue
raise ex
--
2.32.0