Merge pull request #1901 from home-assistant/dev

Release 232
This commit is contained in:
Pascal Vizeli 2020-08-12 18:44:04 +02:00 committed by GitHub
commit d392b35fdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 256 additions and 124 deletions

View File

@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v2.1.1
uses: actions/setup-python@v2.1.2
with:
python-version: ${{ matrix.python-version }}
- name: Restore Python virtual environment
@ -69,7 +69,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.1
uses: actions/setup-python@v2.1.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -113,7 +113,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.1
uses: actions/setup-python@v2.1.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -157,7 +157,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.1
uses: actions/setup-python@v2.1.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -189,7 +189,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.1
uses: actions/setup-python@v2.1.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -230,7 +230,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.1
uses: actions/setup-python@v2.1.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -274,7 +274,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.1
uses: actions/setup-python@v2.1.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -306,7 +306,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.1
uses: actions/setup-python@v2.1.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -350,7 +350,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2.1.1
uses: actions/setup-python@v2.1.2
id: python
with:
python-version: ${{ matrix.python-version }}
@ -391,7 +391,7 @@ jobs:
-o console_output_style=count \
tests
- name: Upload coverage artifact
uses: actions/upload-artifact@v2.1.3
uses: actions/upload-artifact@v2.1.4
with:
name: coverage-${{ matrix.python-version }}
path: .coverage
@ -404,7 +404,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.1
uses: actions/setup-python@v2.1.2
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}

3
API.md
View File

@ -45,6 +45,8 @@ Shows the installed add-ons from `addons`.
"arch": "armhf|aarch64|i386|amd64",
"channel": "stable|beta|dev",
"timezone": "TIMEZONE",
"healthy": "bool",
"supported": "bool",
"logging": "debug|info|warning|error|critical",
"ip_address": "ip address",
"wait_boot": "int",
@ -785,6 +787,7 @@ return:
"machine": "type",
"arch": "arch",
"supported_arch": ["arch1", "arch2"],
"supported": "bool",
"channel": "stable|beta|dev",
"logging": "debug|info|warning|error|critical",
"timezone": "Europe/Zurich"

@ -1 +1 @@
Subproject commit dec1f99a5fbfdafb348d30fa0f7a03f84b0d5d9e
Subproject commit 77b25f5132820c0596ccae82dd501ce67f101e72

View File

@ -5,8 +5,8 @@ cchardet==2.1.6
colorlog==4.2.1
cpe==1.2.1
cryptography==3.0
debugpy==1.0.0b12
docker==4.2.2
debugpy==1.0.0rc1
docker==4.3.0
gitpython==3.1.7
jinja2==2.11.2
packaging==20.4

View File

@ -98,6 +98,7 @@ class AddonManager(CoreSysAttributes):
await addon.start()
except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("Can't start Add-on %s: %s", addon.slug, err)
self.sys_capture_exception(err)
await asyncio.sleep(self.sys_config.wait_boot)
@ -121,6 +122,7 @@ class AddonManager(CoreSysAttributes):
await addon.stop()
except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("Can't stop Add-on %s: %s", addon.slug, err)
self.sys_capture_exception(err)
async def install(self, slug: str) -> None:
"""Install an add-on."""

View File

@ -424,6 +424,11 @@ def _nested_validate_list(coresys, typ, data_list, key):
"""Validate nested items."""
options = []
# Make sure it is a list
if not isinstance(data_list, list):
raise vol.Invalid(f"Invalid list for {key}")
# Process list
for element in data_list:
# Nested?
if isinstance(typ, dict):
@ -439,6 +444,11 @@ def _nested_validate_dict(coresys, typ, data_dict, key):
"""Validate nested items."""
options = {}
# Make sure it is a dict
if not isinstance(data_dict, dict):
raise vol.Invalid(f"Invalid dict for {key}")
# Process dict
for c_key, c_value in data_dict.items():
# Ignore unknown options / remove from list
if c_key not in typ:

View File

@ -14,6 +14,7 @@ from ..const import (
ATTR_LOGGING,
ATTR_MACHINE,
ATTR_SUPERVISOR,
ATTR_SUPPORTED,
ATTR_SUPPORTED_ARCH,
ATTR_TIMEZONE,
)
@ -38,6 +39,7 @@ class APIInfo(CoreSysAttributes):
ATTR_MACHINE: self.sys_machine,
ATTR_ARCH: self.sys_arch.default,
ATTR_SUPPORTED_ARCH: self.sys_arch.supported,
ATTR_SUPPORTED: self.sys_supported,
ATTR_CHANNEL: self.sys_updater.channel,
ATTR_LOGGING: self.sys_config.logging,
ATTR_TIMEZONE: self.sys_timezone,

View File

@ -191,7 +191,11 @@ class APIIngress(CoreSysAttributes):
async for data in result.content.iter_chunked(4096):
await response.write(data)
except (aiohttp.ClientError, aiohttp.ClientPayloadError) as err:
except (
aiohttp.ClientError,
aiohttp.ClientPayloadError,
ConnectionResetError,
) as err:
_LOGGER.error("Stream error with %s: %s", url, err)
return response

View File

@ -1,9 +1,9 @@
try {
new Function("import('/api/hassio/app/frontend_latest/entrypoint.a14fe829.js')")();
new Function("import('/api/hassio/app/frontend_latest/entrypoint.855567b9.js')")();
} catch (err) {
var el = document.createElement('script');
el.src = '/api/hassio/app/frontend_es5/entrypoint.24698450.js';
el.src = '/api/hassio/app/frontend_es5/entrypoint.19035830.js';
document.body.appendChild(el);
}

View File

@ -0,0 +1 @@
{"version":3,"file":"entrypoint.19035830.js","sources":["webpack:///entrypoint.19035830.js"],"mappings":";AAAA","sourceRoot":""}

View File

@ -1 +0,0 @@
{"version":3,"file":"entrypoint.24698450.js","sources":["webpack:///entrypoint.24698450.js"],"mappings":";AAAA","sourceRoot":""}

View File

@ -1,3 +1,3 @@
{
"entrypoint.js": "/api/hassio/app/frontend_es5/entrypoint.24698450.js"
"entrypoint.js": "/api/hassio/app/frontend_es5/entrypoint.19035830.js"
}

View File

@ -1,3 +1,3 @@
{
"entrypoint.js": "/api/hassio/app/frontend_latest/entrypoint.a14fe829.js"
"entrypoint.js": "/api/hassio/app/frontend_latest/entrypoint.855567b9.js"
}

View File

@ -18,6 +18,7 @@ from ..const import (
ATTR_DEBUG_BLOCK,
ATTR_DESCRIPTON,
ATTR_DIAGNOSTICS,
ATTR_HEALTHY,
ATTR_ICON,
ATTR_INSTALLED,
ATTR_IP_ADDRESS,
@ -32,6 +33,7 @@ from ..const import (
ATTR_REPOSITORY,
ATTR_SLUG,
ATTR_STATE,
ATTR_SUPPORTED,
ATTR_TIMEZONE,
ATTR_VERSION,
ATTR_VERSION_LATEST,
@ -98,6 +100,8 @@ class APISupervisor(CoreSysAttributes):
ATTR_VERSION_LATEST: self.sys_updater.version_supervisor,
ATTR_CHANNEL: self.sys_updater.channel,
ATTR_ARCH: self.sys_supervisor.arch,
ATTR_SUPPORTED: self.sys_supported,
ATTR_HEALTHY: self.sys_core.healthy,
ATTR_IP_ADDRESS: str(self.sys_supervisor.ip_address),
ATTR_WAIT_BOOT: self.sys_config.wait_boot,
ATTR_TIMEZONE: self.sys_config.timezone,

View File

@ -280,10 +280,11 @@ def supervisor_debugger(coresys: CoreSys) -> None:
def setup_diagnostics(coresys: CoreSys) -> None:
"""Sentry diagnostic backend."""
_LOGGER.info("Initialize Supervisor Sentry")
def filter_data(event, hint):
# Ignore issue if system is not supported or diagnostics is disabled
if not coresys.config.diagnostics or not coresys.core.healthy:
if not coresys.config.diagnostics or not coresys.supported:
return None
# Not full startup - missing information
@ -291,10 +292,9 @@ def setup_diagnostics(coresys: CoreSys) -> None:
return event
# Update information
with sentry_sdk.configure_scope() as scope:
scope.set_context(
"supervisor",
{
event.setdefault("extra", {}).update(
{
"supervisor": {
"machine": coresys.machine,
"arch": coresys.arch.default,
"docker": coresys.docker.info.version,
@ -308,23 +308,27 @@ def setup_diagnostics(coresys: CoreSys) -> None:
"dns": coresys.plugins.dns.version,
"multicast": coresys.plugins.multicast.version,
"cli": coresys.plugins.cli.version,
},
)
scope.set_tag(
"installation_type",
f"{'os' if coresys.hassos.available else 'supervised'}",
)
}
}
)
event.setdefault("tags", {}).update(
{
"installation_type": "os" if coresys.hassos.available else "supervised",
"machine": coresys.machine,
}
)
return event
# Set log level
sentry_logging = LoggingIntegration(
level=logging.ERROR, event_level=logging.CRITICAL
level=logging.WARNING, event_level=logging.CRITICAL
)
sentry_sdk.init(
dsn="https://9c6ea70f49234442b4746e447b24747e@o427061.ingest.sentry.io/5370612",
before_send=filter_data,
max_breadcrumbs=30,
integrations=[AioHttpIntegration(), sentry_logging],
release=SUPERVISOR_VERSION,
)

View File

@ -3,8 +3,7 @@ from enum import Enum
from ipaddress import ip_network
from pathlib import Path
SUPERVISOR_VERSION = "231"
SUPERVISOR_VERSION = "232"
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
URL_HASSIO_VERSION = "https://version.home-assistant.io/{channel}.json"
@ -243,6 +242,8 @@ ATTR_ACTIVE = "active"
ATTR_APPLICATION = "application"
ATTR_INIT = "init"
ATTR_DIAGNOSTICS = "diagnostics"
ATTR_HEALTHY = "healthy"
ATTR_SUPPORTED = "supported"
PROVIDE_SERVICE = "provide"
NEED_SERVICE = "need"

View File

@ -19,45 +19,56 @@ class Core(CoreSysAttributes):
"""Initialize Supervisor object."""
self.coresys: CoreSys = coresys
self.state: CoreStates = CoreStates.INITIALIZE
self.healthy: bool = True
self._healthy: bool = True
@property
def healthy(self) -> bool:
"""Return True if system is healthy."""
return self._healthy and self.sys_supported
async def connect(self):
"""Connect Supervisor container."""
await self.sys_supervisor.load()
# If a update is failed?
if self.sys_dev:
self.sys_config.version = self.sys_supervisor.version
elif self.sys_config.version != self.sys_supervisor.version:
self.healthy = False
_LOGGER.critical("Update of Supervisor fails!")
# If local docker is supported?
# If host docker is supported?
if not self.sys_docker.info.supported_version:
self.healthy = False
_LOGGER.critical(
self.coresys.supported = False
_LOGGER.error(
"Docker version %s is not supported by Supervisor!",
self.sys_docker.info.version,
)
elif self.sys_docker.info.inside_lxc:
self.healthy = False
_LOGGER.critical(
self.coresys.supported = False
_LOGGER.error(
"Detected Docker running inside LXC. Running Home Assistant with the Supervisor on LXC is not supported!"
)
self.sys_docker.info.check_requirements()
# Dbus available
if not SOCKET_DBUS.exists():
self.healthy = False
_LOGGER.critical(
self.coresys.supported = False
_LOGGER.error(
"DBus is required for Home Assistant. This system is not supported!"
)
# Check if system is healthy
if not self.healthy:
_LOGGER.critical(
"System running in a unhealthy state. Please update you OS or software!"
# Check supervisor version/update
if self.sys_dev:
self.sys_config.version = self.sys_supervisor.version
elif (
self.sys_config.version == "dev"
or self.sys_supervisor.instance.version == "dev"
):
self.coresys.supported = False
_LOGGER.warning(
"Found a development supervisor outside dev channel (%s)",
self.sys_updater.channel,
)
elif self.sys_config.version != self.sys_supervisor.version:
self._healthy = False
_LOGGER.error(
"Update %s of Supervisor %s fails!",
self.sys_config.version,
self.sys_supervisor.version,
)
async def setup(self):
@ -114,6 +125,14 @@ class Core(CoreSysAttributes):
self.state = CoreStates.STARTUP
await self.sys_api.start()
# Check if system is healthy
if not self.sys_supported:
_LOGGER.critical("System running in a unsupported environment!")
elif not self.healthy:
_LOGGER.critical(
"System running in a unhealthy state and need manual intervention!"
)
# Mark booted partition as healthy
if self.sys_hassos.available:
await self.sys_hassos.mark_healthy()

View File

@ -44,10 +44,13 @@ class CoreSys:
def __init__(self):
"""Initialize coresys."""
# Static attributes
# Static attributes protected
self._machine_id: Optional[str] = None
self._machine: Optional[str] = None
# Static attributes
self.supported: bool = True
# External objects
self._loop: asyncio.BaseEventLoop = asyncio.get_running_loop()
self._websession: aiohttp.ClientSession = aiohttp.ClientSession()
@ -459,6 +462,11 @@ class CoreSysAttributes:
"""Return True if we run dev mode."""
return self.coresys.dev
@property
def sys_supported(self) -> bool:
"""Return True if the system is supported."""
return self.coresys.supported
@property
def sys_timezone(self) -> str:
"""Return timezone."""

View File

@ -0,0 +1,65 @@
"""Test add-ons schema to UI schema convertion."""
import pytest
import voluptuous as vol
from supervisor.addons.validate import validate_options
def test_simple_schema(coresys):
"""Test with simple schema."""
assert validate_options(
coresys,
{"name": "str", "password": "password", "fires": "bool", "alias": "str?"},
)({"name": "Pascal", "password": "1234", "fires": True, "alias": "test"})
assert validate_options(
coresys,
{"name": "str", "password": "password", "fires": "bool", "alias": "str?"},
)({"name": "Pascal", "password": "1234", "fires": True})
with pytest.raises(vol.error.Invalid):
validate_options(
coresys,
{"name": "str", "password": "password", "fires": "bool", "alias": "str?"},
)({"name": "Pascal", "password": "1234", "fires": "hah"})
with pytest.raises(vol.error.Invalid):
validate_options(
coresys,
{"name": "str", "password": "password", "fires": "bool", "alias": "str?"},
)({"name": "Pascal", "fires": True})
def test_complex_schema_list(coresys):
"""Test with complex list schema."""
assert validate_options(
coresys, {"name": "str", "password": "password", "extend": ["str"]},
)({"name": "Pascal", "password": "1234", "extend": ["test", "blu"]})
with pytest.raises(vol.error.Invalid):
validate_options(
coresys, {"name": "str", "password": "password", "extend": ["str"]},
)({"name": "Pascal", "password": "1234", "extend": ["test", 1]})
with pytest.raises(vol.error.Invalid):
validate_options(
coresys, {"name": "str", "password": "password", "extend": ["str"]},
)({"name": "Pascal", "password": "1234", "extend": "test"})
def test_complex_schema_dict(coresys):
"""Test with complex dict schema."""
assert validate_options(
coresys, {"name": "str", "password": "password", "extend": {"test": "int"}},
)({"name": "Pascal", "password": "1234", "extend": {"test": 1}})
with pytest.raises(vol.error.Invalid):
validate_options(
coresys, {"name": "str", "password": "password", "extend": {"test": "int"}},
)({"name": "Pascal", "password": "1234", "extend": {"wrong": 1}})
with pytest.raises(vol.error.Invalid):
validate_options(
coresys, {"name": "str", "password": "password", "extend": ["str"]},
)({"name": "Pascal", "password": "1234", "extend": "test"})