Compare commits

...

110 Commits
0.93 ... 0.100

Author SHA1 Message Date
Pascal Vizeli
a2d3ee0d67 Fix version conflict 2018-04-10 21:18:47 +02:00
Pascal Vizeli
d29fab69e8 Merge remote-tracking branch 'origin/dev' into rc 2018-04-09 23:46:52 +02:00
Pascal Vizeli
6205f40298 Pump version to 0.100 2018-04-09 23:45:33 +02:00
Pascal Vizeli
6b169f3f17 Fix version 2018-04-09 23:44:52 +02:00
Pascal Vizeli
0d4a5a7ffb Fix version conflict 2018-04-09 23:41:00 +02:00
Pascal Vizeli
dac90d29dd Update Hass.io to version 1.0 2018-04-09 23:39:19 +02:00
Pascal Vizeli
7e815633e7 Merge pull request #437 from home-assistant/fix_restart
Abstract restart logic
2018-04-09 23:31:55 +02:00
Pascal Vizeli
f062f31ca2 Fix logic 2018-04-09 23:10:12 +02:00
Pascal Vizeli
1374f90433 cleanup version 2018-04-09 22:52:16 +02:00
Pascal Vizeli
b692b19a4d Fix log bug 2018-04-09 22:50:21 +02:00
Pascal Vizeli
92d5b14cf5 Abstract restart logic 2018-04-09 22:13:16 +02:00
Pascal Vizeli
6a84829c16 Merge pull request #436 from home-assistant/aiohttp_update
Update aioHttp 3.1.2 & Handling
2018-04-09 21:29:21 +02:00
Pascal Vizeli
7036ecbd0a Update aioHttp 3.1.2 & Handling 2018-04-09 21:01:06 +02:00
Pascal Vizeli
19b5059972 Pump version 1.0 2018-04-09 20:23:04 +02:00
Pascal Vizeli
cebc377fa7 Merge pull request #435 from home-assistant/fix_docker_char
Bugfix, remove unsupported characters
2018-04-09 20:22:03 +02:00
Pascal Vizeli
d36c3919d7 Update docker 2018-04-09 20:01:48 +02:00
Pascal Vizeli
0684427373 Bugfix, remove unsupported characters 2018-04-08 23:30:42 +02:00
Pascal Vizeli
8ff79e85bf Merge pull request #433 from home-assistant/secure
Add support for Seccomp/AppArmor profiles
2018-04-08 23:07:33 +02:00
Pascal Vizeli
ee4b28a490 Fix's & cleanup 2018-04-08 22:27:58 +02:00
Pascal Vizeli
fddd5b8860 Fix lint 2018-04-07 00:32:54 +02:00
Pascal Vizeli
72279072ac Add support for Seccomp/AppArmor profiles 2018-04-07 00:24:23 +02:00
Pascal Vizeli
0b70448273 Update Home-Assistant 0.67.0b0 2018-04-06 23:20:02 +02:00
Pascal Vizeli
4eb24fcbc5 Update Home-Assistant 0.67.0b0 2018-04-06 23:19:31 +02:00
Pascal Vizeli
06edf59d14 Update Home-Assistant to version 0.66.1 2018-04-02 09:47:39 +02:00
Pascal Vizeli
36ca851bc2 Update Home-Assistant to version 0.66.1 2018-04-02 09:47:21 +02:00
Pascal Vizeli
a4e453bf83 Update Home-Assistant to version 0.66.1 2018-04-02 09:47:05 +02:00
Pascal Vizeli
d211eec66f Update Home-Assistant to version 0.66.1b0 2018-04-01 11:08:34 +02:00
Pascal Vizeli
db8540d4ab Update Home-Assistant to version 0.66.1b0 2018-04-01 11:07:55 +02:00
Pascal Vizeli
30e270e7c0 Update Home-Assistant to version 0.66.0 2018-03-31 09:45:53 +02:00
Pascal Vizeli
9734307551 Update Home-Assistant to version 0.66.0 2018-03-31 09:45:32 +02:00
Pascal Vizeli
c650f8d1e1 Update Home-Assistant to version 0.66.0 2018-03-31 09:45:15 +02:00
Pascal Vizeli
10005898f8 Fix tag name 2018-03-30 15:10:35 +02:00
Pascal Vizeli
716389e0c1 Fix tag format 2018-03-30 15:10:02 +02:00
Pascal Vizeli
658729feb5 Update Home-Assistant to version 0.66.0.b3 2018-03-30 10:23:10 +02:00
Pascal Vizeli
ae7808eb2a Update Home-Assistant to version 0.66.0.b3 2018-03-30 10:22:41 +02:00
Pascal Vizeli
d8e0e9e0b0 Update Home-Assistant to version 0.66.0.b2 2018-03-27 09:24:59 +02:00
Pascal Vizeli
a860a3c122 Update Home-Assistant to version 0.66.0.b2 2018-03-27 09:24:38 +02:00
Pascal Vizeli
fe60d526b9 Revert home-assistant version 2018-03-24 23:34:22 +01:00
Pascal Vizeli
769904778f Merge remote-tracking branch 'origin/rc' 2018-03-24 23:32:37 +01:00
Pascal Vizeli
a3a40c79d6 Fix merge conflicts 2018-03-24 23:31:26 +01:00
Pascal Vizeli
b44f613136 Update hass.io to version 0.99 2018-03-24 23:09:32 +01:00
Pascal Vizeli
801be9c60b Create .gitattributes 2018-03-24 23:05:58 +01:00
Pascal Vizeli
b6db6a1287 Create .gitattributes 2018-03-24 23:04:37 +01:00
Pascal Vizeli
4181174bcc Create .gitattributes 2018-03-24 23:03:15 +01:00
Pascal Vizeli
3be46e6011 Update Home-Assistant to version 0.66.0.beta0 2018-03-24 22:58:52 +01:00
Pascal Vizeli
98b93efc5c Merge pull request #423 from home-assistant/beta
Change Upstream handling
2018-03-24 22:56:04 +01:00
Pascal Vizeli
6156019c2f Merge pull request #424 from home-assistant/ui-99
Update pannel for 0.99
2018-03-24 22:50:33 +01:00
Pascal Vizeli
80d60148a9 Update pannel for 0.99 2018-03-24 22:48:45 +01:00
Pascal Vizeli
8baf59a608 fix lint 2018-03-24 22:06:56 +01:00
Pascal Vizeli
b546365aaa Fix 2018-03-24 22:05:01 +01:00
Pascal Vizeli
0a68698912 rename mode to channel 2018-03-24 22:01:13 +01:00
Pascal Vizeli
45288a2491 Change Upstream handling 2018-03-24 21:44:44 +01:00
Pascal Vizeli
f34a175e4f Update Home-Assistant to version 0.66.0.beta0 2018-03-24 10:34:53 +01:00
Pascal Vizeli
6e7e145822 Update Home-Assistant to version 0.65.6 2018-03-21 22:19:38 +01:00
Pascal Vizeli
9abebe2d5d Update Home-Assistant to version 0.65.6 2018-03-21 22:19:08 +01:00
Pascal Vizeli
b0c5884c3f Update Home-Assistant to version 0.65.5 2018-03-15 12:11:01 +01:00
Pascal Vizeli
a79e6a8eea Update Home-Assistant to version 0.65.5 2018-03-15 12:10:16 +01:00
Pascal Vizeli
c1f1aed9ca Pump version to 0.99 2018-03-14 22:25:58 +01:00
Pascal Vizeli
65b0e17b5b Merge pull request #414 from home-assistant/dev
Release 0.98
2018-03-14 22:25:14 +01:00
Pascal Vizeli
6947131b47 Update Hass.io to version 0.98 2018-03-14 22:10:13 +01:00
Pascal Vizeli
914dd53da0 Merge pull request #411 from home-assistant/fix_watchdog
Use lock on homeassistant level
2018-03-14 21:52:20 +01:00
Pascal Vizeli
58616ef686 bugfix aiohttp 2018-03-14 21:12:08 +01:00
Pascal Vizeli
563e0c1e0e fix wrong startup blocking 2018-03-14 19:08:03 +01:00
Pascal Vizeli
437070fd7a Merge pull request #412 from home-assistant/fix-geoip
Fix URL for freegeoip
2018-03-13 23:34:06 +01:00
Pascal Vizeli
baa9cf451c Fix URL for freegeoip 2018-03-13 23:28:38 +01:00
Pascal Vizeli
c2918d4519 Use lock on homeassistant level 2018-03-13 23:09:53 +01:00
Pascal Vizeli
1efdcd4691 Merge remote-tracking branch 'origin/master' into dev 2018-03-13 21:31:56 +01:00
Pascal Vizeli
2a43087ed7 Pump version to 0.98 2018-03-13 16:06:44 +01:00
Pascal Vizeli
5716324934 Merge pull request #410 from home-assistant/dev
Release 0.97
2018-03-13 16:05:31 +01:00
Pascal Vizeli
ae267e0380 Merge branch 'master' into dev 2018-03-13 14:09:13 +01:00
Pascal Vizeli
3918a2a228 Update Home-Assistant version 0.65.4 2018-03-13 14:07:21 +01:00
Pascal Vizeli
e375fc36d3 Update Hass.io to version 0.97 2018-03-13 00:09:57 +01:00
Pascal Vizeli
f5e29b4651 Update panel to last (#408) 2018-03-12 23:51:09 +01:00
Pascal Vizeli
524d875516 Update aioHttp3 (#403)
* Update aioHttp3

* fix line ending

* fix close session
2018-03-12 23:40:06 +01:00
Pascal Vizeli
60bdc00ce9 Update Home-Assistant to version 0.65.3 2018-03-12 07:13:47 +01:00
Pascal Vizeli
073166190f Update Home-Assistant to version 0.65.3 2018-03-12 07:13:27 +01:00
Pascal Vizeli
b80e4d7d70 Update Home-Assistant to version 0.65.2 2018-03-11 23:58:16 +01:00
Pascal Vizeli
cc434e27cf Update Home-Assistant to version 0.65.2 2018-03-11 23:57:57 +01:00
Pascal Vizeli
8377e04b62 Update Home-Assistant to version 0.65.1 2018-03-11 20:32:43 +01:00
Pascal Vizeli
0a47fb9c83 Update Home-Assistant to version 0.65.1 2018-03-11 20:32:25 +01:00
Pascal Vizeli
a5d3c850e9 Update Home-Assistant to version 0.65.0 2018-03-09 23:32:47 +01:00
Pascal Vizeli
d6391f62be Update Home-Assistant to version 0.65.0 2018-03-09 23:10:27 +01:00
Pascal Vizeli
c6f302e448 Update ResinOS to version 1.3 2018-03-05 22:51:44 +01:00
Pascal Vizeli
9706022c21 Update ResinOS to version 1.3 2018-03-05 22:51:08 +01:00
Pascal Vizeli
1d858f4920 Update ResinOS to version 1.2 2018-03-04 00:43:24 +01:00
Pascal Vizeli
e09ba30d46 Update ResinOS to version 1.2 2018-03-04 00:43:00 +01:00
mark9white
38ec3d14ed Allow addons that require IPC_LOCK capability (#397) 2018-03-03 23:06:42 +01:00
Pascal Vizeli
8ee9380cc7 Pump version to 0.97 2018-03-03 11:15:39 +01:00
Pascal Vizeli
6e74e4c008 Fix version conflicts 2018-03-03 11:12:59 +01:00
Pascal Vizeli
5ebc58851b Update Hass.io to version 0.96 2018-03-03 11:08:00 +01:00
Pascal Vizeli
16b09bbfc5 Allow to use branch on repositories (#395)
* Allow to use branch on repositories

* Fix argument extraction

* fix lint
2018-03-03 11:00:58 +01:00
Pascal Vizeli
d4b5fc79f4 Update Home-Assistant to version 0.64.3 2018-03-03 00:07:04 +01:00
Pascal Vizeli
e51c044ccd Update Home-Assistant to version 0.64.3 2018-03-02 23:56:48 +01:00
Pascal Vizeli
d3b1ba81f7 Update panel for encrypted backups (#394)
* Update panel for encrypted backups

* fix lint
2018-03-02 23:23:40 +01:00
Pascal Vizeli
26f55f02c0 Update Home-Assistant to version 0.64.2 2018-03-02 07:01:42 +01:00
Pascal Vizeli
8050707ff9 Update Home-Assistant to version 0.64.2 2018-03-02 06:54:32 +01:00
c727
46252030cf Improve names for built-in repos (#391) 2018-03-01 19:00:21 +01:00
Pascal Vizeli
681fa835ef Update Home-Assistant to version 0.64.1 2018-02-28 08:16:18 +01:00
Pascal Vizeli
d6560eb976 Update Home-Assistant to version 0.64.1 2018-02-28 07:48:54 +01:00
Pascal Vizeli
3770b307af Pump version to 0.96 2018-02-26 22:55:53 +01:00
Pascal Vizeli
0dacbb31be Fix version conflicts 2018-02-26 22:53:31 +01:00
Pascal Vizeli
bbdbd756a7 Update Hass.io to version 0.95 2018-02-26 22:42:29 +01:00
Pascal Vizeli
508e38e622 Fix snapshot partial API (#389) 2018-02-26 22:26:39 +01:00
Pascal Vizeli
ffe45d0d02 Bugfix if no data is given for encryption (#387)
* Bugfix if no data is given for encryption

* Update snapshot.py
2018-02-26 22:17:25 +01:00
Pascal Vizeli
9206d1acf8 Update Home-Assistant to version 0.64 2018-02-26 06:10:40 +01:00
Pascal Vizeli
da867ef8ef Update Home-Assistant to version 0.64 2018-02-26 06:03:24 +01:00
Pascal Vizeli
4826201e51 Pump version to 0.95 2018-02-25 12:57:53 +01:00
Pascal Vizeli
463c97f9e7 Update Hass.io to version 0.94 2018-02-25 12:49:39 +01:00
Pascal Vizeli
3983928c6c Bugfix snapshot dialog (#380) 2018-02-25 12:18:05 +01:00
Pascal Vizeli
15e626027f Pump version to 0.94 2018-02-24 08:50:21 +01:00
32 changed files with 400 additions and 317 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Ignore version on merge
version.json merge=ours

6
API.md
View File

@@ -36,7 +36,7 @@ The addons from `addons` are only installed one.
"version": "INSTALL_VERSION", "version": "INSTALL_VERSION",
"last_version": "LAST_VERSION", "last_version": "LAST_VERSION",
"arch": "armhf|aarch64|i386|amd64", "arch": "armhf|aarch64|i386|amd64",
"beta_channel": "true|false", "channel": "stable|beta|dev",
"timezone": "TIMEZONE", "timezone": "TIMEZONE",
"wait_boot": "int", "wait_boot": "int",
"addons": [ "addons": [
@@ -72,7 +72,7 @@ Optional:
```json ```json
{ {
"beta_channel": "true|false", "channel": "stable|beta|dev",
"timezone": "TIMEZONE", "timezone": "TIMEZONE",
"wait_boot": "int", "wait_boot": "int",
"addons_repositories": [ "addons_repositories": [
@@ -427,6 +427,8 @@ Get all available addons.
"host_ipc": "bool", "host_ipc": "bool",
"host_dbus": "bool", "host_dbus": "bool",
"privileged": ["NET_ADMIN", "SYS_ADMIN"], "privileged": ["NET_ADMIN", "SYS_ADMIN"],
"seccomp": "disable|default|profile",
"apparmor": "disable|default|profile",
"devices": ["/dev/xy"], "devices": ["/dev/xy"],
"auto_uart": "bool", "auto_uart": "bool",
"icon": "bool", "icon": "bool",

View File

@@ -23,7 +23,9 @@ from ..const import (
ATTR_STATE, ATTR_TIMEOUT, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_WEBUI, ATTR_STATE, ATTR_TIMEOUT, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_WEBUI,
ATTR_HASSIO_API, ATTR_AUDIO, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT, ATTR_HASSIO_API, ATTR_AUDIO, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT,
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC,
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES) ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES,
ATTR_SECCOMP, ATTR_APPARMOR, SECURITY_PROFILE, SECURITY_DISABLE,
SECURITY_DEFAULT)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..docker.addon import DockerAddon from ..docker.addon import DockerAddon
from ..utils.json import write_json_file, read_json_file from ..utils.json import write_json_file, read_json_file
@@ -316,6 +318,24 @@ class Addon(CoreSysAttributes):
"""Return list of privilege.""" """Return list of privilege."""
return self._mesh.get(ATTR_PRIVILEGED) return self._mesh.get(ATTR_PRIVILEGED)
@property
def seccomp(self):
"""Return True if seccomp is enabled."""
if not self._mesh.get(ATTR_SECCOMP):
return SECURITY_DISABLE
elif self.path_seccomp.exists():
return SECURITY_PROFILE
return SECURITY_DEFAULT
@property
def apparmor(self):
"""Return True if seccomp is enabled."""
if not self._mesh.get(ATTR_APPARMOR):
return SECURITY_DISABLE
elif self.path_apparmor.exists():
return SECURITY_PROFILE
return SECURITY_DEFAULT
@property @property
def legacy(self): def legacy(self):
"""Return if the add-on don't support hass labels.""" """Return if the add-on don't support hass labels."""
@@ -474,6 +494,16 @@ class Addon(CoreSysAttributes):
"""Return path to addon changelog.""" """Return path to addon changelog."""
return Path(self.path_location, 'CHANGELOG.md') return Path(self.path_location, 'CHANGELOG.md')
@property
def path_seccomp(self):
"""Return path to custom seccomp profile."""
return Path(self.path_location, 'seccomp.json')
@property
def path_apparmor(self):
"""Return path to custom AppArmor profile."""
return Path(self.path_location, 'apparmor')
def save_data(self): def save_data(self):
"""Save data of addon.""" """Save data of addon."""
self._addons.data.save_data() self._addons.data.save_data()

View File

@@ -55,8 +55,8 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
'io.hass.version': version, 'io.hass.version': version,
'io.hass.arch': self._arch, 'io.hass.arch': self._arch,
'io.hass.type': META_ADDON, 'io.hass.type': META_ADDON,
'io.hass.name': self.addon.name, 'io.hass.name': self._fix_label('name'),
'io.hass.description': self.addon.description, 'io.hass.description': self._fix_label('description'),
}, },
'buildargs': { 'buildargs': {
'BUILD_FROM': self.base_image, 'BUILD_FROM': self.base_image,
@@ -70,3 +70,8 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
args['labels']['io.hass.url'] = self.addon.url args['labels']['io.hass.url'] = self.addon.url
return args return args
def _fix_label(self, label_name):
"""Remove characters they are not supported."""
label = getattr(self.addon, label_name, "")
return label.replace("'", "")

View File

@@ -1,12 +1,12 @@
{ {
"local": { "local": {
"name": "Local Add-Ons", "name": "Local add-ons",
"url": "https://home-assistant.io/hassio", "url": "https://home-assistant.io/hassio",
"maintainer": "you" "maintainer": "you"
}, },
"core": { "core": {
"name": "Built-in Add-Ons", "name": "Official add-ons",
"url": "https://home-assistant.io/addons", "url": "https://home-assistant.io/addons",
"maintainer": "Home Assistant authors" "maintainer": "Home Assistant"
} }
} }

View File

@@ -8,8 +8,9 @@ import shutil
import git import git
from .utils import get_hash_from_repository from .utils import get_hash_from_repository
from ..const import URL_HASSIO_ADDONS from ..const import URL_HASSIO_ADDONS, ATTR_URL, ATTR_BRANCH
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..validate import RE_REPOSITORY
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -22,9 +23,20 @@ class GitRepo(CoreSysAttributes):
self.coresys = coresys self.coresys = coresys
self.repo = None self.repo = None
self.path = path self.path = path
self.url = url
self.lock = asyncio.Lock(loop=coresys.loop) self.lock = asyncio.Lock(loop=coresys.loop)
self._data = RE_REPOSITORY.match(url).groupdict()
@property
def url(self):
"""Return repository URL."""
return self._data[ATTR_URL]
@property
def branch(self):
"""Return repository branch."""
return self._data[ATTR_BRANCH]
async def load(self): async def load(self):
"""Init git addon repo.""" """Init git addon repo."""
if not self.path.is_dir(): if not self.path.is_dir():
@@ -46,12 +58,20 @@ class GitRepo(CoreSysAttributes):
async def clone(self): async def clone(self):
"""Clone git addon repo.""" """Clone git addon repo."""
async with self.lock: async with self.lock:
git_args = {
attribute: value
for attribute, value in (
('recursive', True),
('branch', self.branch)
) if value is not None
}
try: try:
_LOGGER.info("Clone addon %s repository", self.url) _LOGGER.info("Clone addon %s repository", self.url)
self.repo = await self._loop.run_in_executor( self.repo = await self._loop.run_in_executor(None, ft.partial(
None, ft.partial( git.Repo.clone_from, self.url, str(self.path),
git.Repo.clone_from, self.url, str(self.path), **git_args
recursive=True)) ))
except (git.InvalidGitRepositoryError, git.NoSuchPathError, except (git.InvalidGitRepositoryError, git.NoSuchPathError,
git.GitCommandError) as err: git.GitCommandError) as err:

View File

@@ -17,7 +17,8 @@ from ..const import (
ATTR_AUTO_UPDATE, ATTR_WEBUI, ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_HOST_IPC, ATTR_AUTO_UPDATE, ATTR_WEBUI, ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_HOST_IPC,
ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API, ATTR_BUILD_FROM, ATTR_SQUASH, ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API, ATTR_BUILD_FROM, ATTR_SQUASH,
ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY,
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY) ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY,
ATTR_SECCOMP, ATTR_APPARMOR)
from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_CHANNEL from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_CHANNEL
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -60,6 +61,7 @@ PRIVILEGED_ALL = [
"NET_ADMIN", "NET_ADMIN",
"SYS_ADMIN", "SYS_ADMIN",
"SYS_RAWIO", "SYS_RAWIO",
"IPC_LOCK",
"SYS_TIME", "SYS_TIME",
"SYS_NICE" "SYS_NICE"
] ]
@@ -106,6 +108,8 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
vol.Optional(ATTR_MAP, default=list): [vol.Match(RE_VOLUME)], vol.Optional(ATTR_MAP, default=list): [vol.Match(RE_VOLUME)],
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)}, vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)},
vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)], vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)],
vol.Optional(ATTR_SECCOMP, default=True): vol.Boolean(),
vol.Optional(ATTR_APPARMOR, default=True): vol.Boolean(),
vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(), vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(),
vol.Optional(ATTR_GPIO, default=False): vol.Boolean(), vol.Optional(ATTR_GPIO, default=False): vol.Boolean(),
vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(), vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(),

View File

@@ -13,7 +13,7 @@ from .proxy import APIProxy
from .supervisor import APISupervisor from .supervisor import APISupervisor
from .snapshots import APISnapshots from .snapshots import APISnapshots
from .services import APIServices from .services import APIServices
from .security import security_layer from .security import SecurityMiddleware
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -25,16 +25,14 @@ class RestAPI(CoreSysAttributes):
def __init__(self, coresys): def __init__(self, coresys):
"""Initialize docker base wrapper.""" """Initialize docker base wrapper."""
self.coresys = coresys self.coresys = coresys
self.security = SecurityMiddleware(coresys)
self.webapp = web.Application( self.webapp = web.Application(
middlewares=[security_layer], loop=self._loop) middlewares=[self.security.token_validation], loop=self._loop)
# service stuff # service stuff
self._handler = None self._handler = None
self.server = None self.server = None
# middleware
self.webapp['coresys'] = coresys
async def load(self): async def load(self):
"""Register REST API Calls.""" """Register REST API Calls."""
self._register_supervisor() self._register_supervisor()
@@ -53,178 +51,163 @@ class RestAPI(CoreSysAttributes):
api_host = APIHost() api_host = APIHost()
api_host.coresys = self.coresys api_host.coresys = self.coresys
self.webapp.router.add_get('/host/info', api_host.info) self.webapp.add_routes([
self.webapp.router.add_get('/host/hardware', api_host.hardware) web.get('/host/info', api_host.info),
self.webapp.router.add_post('/host/reboot', api_host.reboot) web.get('/host/hardware', api_host.hardware),
self.webapp.router.add_post('/host/shutdown', api_host.shutdown) web.post('/host/reboot', api_host.reboot),
self.webapp.router.add_post('/host/update', api_host.update) web.post('/host/shutdown', api_host.shutdown),
self.webapp.router.add_post('/host/options', api_host.options) web.post('/host/update', api_host.update),
self.webapp.router.add_post('/host/reload', api_host.reload) web.post('/host/options', api_host.options),
web.post('/host/reload', api_host.reload),
])
def _register_network(self): def _register_network(self):
"""Register network function.""" """Register network function."""
api_net = APINetwork() api_net = APINetwork()
api_net.coresys = self.coresys api_net.coresys = self.coresys
self.webapp.router.add_get('/network/info', api_net.info) self.webapp.add_routes([
self.webapp.router.add_post('/network/options', api_net.options) web.get('/network/info', api_net.info),
web.post('/network/options', api_net.options),
])
def _register_supervisor(self): def _register_supervisor(self):
"""Register supervisor function.""" """Register supervisor function."""
api_supervisor = APISupervisor() api_supervisor = APISupervisor()
api_supervisor.coresys = self.coresys api_supervisor.coresys = self.coresys
self.webapp.router.add_get('/supervisor/ping', api_supervisor.ping) self.webapp.add_routes([
self.webapp.router.add_get('/supervisor/info', api_supervisor.info) web.get('/supervisor/ping', api_supervisor.ping),
self.webapp.router.add_get('/supervisor/stats', api_supervisor.stats) web.get('/supervisor/info', api_supervisor.info),
self.webapp.router.add_post( web.get('/supervisor/stats', api_supervisor.stats),
'/supervisor/update', api_supervisor.update) web.get('/supervisor/logs', api_supervisor.logs),
self.webapp.router.add_post( web.post('/supervisor/update', api_supervisor.update),
'/supervisor/reload', api_supervisor.reload) web.post('/supervisor/reload', api_supervisor.reload),
self.webapp.router.add_post( web.post('/supervisor/options', api_supervisor.options),
'/supervisor/options', api_supervisor.options) ])
self.webapp.router.add_get('/supervisor/logs', api_supervisor.logs)
def _register_homeassistant(self): def _register_homeassistant(self):
"""Register homeassistant function.""" """Register homeassistant function."""
api_hass = APIHomeAssistant() api_hass = APIHomeAssistant()
api_hass.coresys = self.coresys api_hass.coresys = self.coresys
self.webapp.router.add_get('/homeassistant/info', api_hass.info) self.webapp.add_routes([
self.webapp.router.add_get('/homeassistant/logs', api_hass.logs) web.get('/homeassistant/info', api_hass.info),
self.webapp.router.add_get('/homeassistant/stats', api_hass.stats) web.get('/homeassistant/logs', api_hass.logs),
self.webapp.router.add_post('/homeassistant/options', api_hass.options) web.get('/homeassistant/stats', api_hass.stats),
self.webapp.router.add_post('/homeassistant/update', api_hass.update) web.post('/homeassistant/options', api_hass.options),
self.webapp.router.add_post('/homeassistant/restart', api_hass.restart) web.post('/homeassistant/update', api_hass.update),
self.webapp.router.add_post('/homeassistant/stop', api_hass.stop) web.post('/homeassistant/restart', api_hass.restart),
self.webapp.router.add_post('/homeassistant/start', api_hass.start) web.post('/homeassistant/stop', api_hass.stop),
self.webapp.router.add_post('/homeassistant/check', api_hass.check) web.post('/homeassistant/start', api_hass.start),
web.post('/homeassistant/check', api_hass.check),
])
def _register_proxy(self): def _register_proxy(self):
"""Register HomeAssistant API Proxy.""" """Register HomeAssistant API Proxy."""
api_proxy = APIProxy() api_proxy = APIProxy()
api_proxy.coresys = self.coresys api_proxy.coresys = self.coresys
self.webapp.router.add_get( self.webapp.add_routes([
'/homeassistant/api/websocket', api_proxy.websocket) web.get('/homeassistant/api/websocket', api_proxy.websocket),
self.webapp.router.add_get( web.get('/homeassistant/websocket', api_proxy.websocket),
'/homeassistant/websocket', api_proxy.websocket) web.get('/homeassistant/api/stream', api_proxy.stream),
self.webapp.router.add_get( web.post('/homeassistant/api/{path:.+}', api_proxy.api),
'/homeassistant/api/stream', api_proxy.stream) web.get('/homeassistant/api/{path:.+}', api_proxy.api),
self.webapp.router.add_post( web.get('/homeassistant/api/', api_proxy.api),
'/homeassistant/api/{path:.+}', api_proxy.api) ])
self.webapp.router.add_get(
'/homeassistant/api/{path:.+}', api_proxy.api)
self.webapp.router.add_get(
'/homeassistant/api/', api_proxy.api)
def _register_addons(self): def _register_addons(self):
"""Register homeassistant function.""" """Register homeassistant function."""
api_addons = APIAddons() api_addons = APIAddons()
api_addons.coresys = self.coresys api_addons.coresys = self.coresys
self.webapp.router.add_get('/addons', api_addons.list) self.webapp.add_routes([
self.webapp.router.add_post('/addons/reload', api_addons.reload) web.get('/addons', api_addons.list),
self.webapp.router.add_get('/addons/{addon}/info', api_addons.info) web.post('/addons/reload', api_addons.reload),
self.webapp.router.add_post( web.get('/addons/{addon}/info', api_addons.info),
'/addons/{addon}/install', api_addons.install) web.post('/addons/{addon}/install', api_addons.install),
self.webapp.router.add_post( web.post('/addons/{addon}/uninstall', api_addons.uninstall),
'/addons/{addon}/uninstall', api_addons.uninstall) web.post('/addons/{addon}/start', api_addons.start),
self.webapp.router.add_post('/addons/{addon}/start', api_addons.start) web.post('/addons/{addon}/stop', api_addons.stop),
self.webapp.router.add_post('/addons/{addon}/stop', api_addons.stop) web.post('/addons/{addon}/restart', api_addons.restart),
self.webapp.router.add_post( web.post('/addons/{addon}/update', api_addons.update),
'/addons/{addon}/restart', api_addons.restart) web.post('/addons/{addon}/options', api_addons.options),
self.webapp.router.add_post( web.post('/addons/{addon}/rebuild', api_addons.rebuild),
'/addons/{addon}/update', api_addons.update) web.get('/addons/{addon}/logs', api_addons.logs),
self.webapp.router.add_post( web.get('/addons/{addon}/icon', api_addons.icon),
'/addons/{addon}/options', api_addons.options) web.get('/addons/{addon}/logo', api_addons.logo),
self.webapp.router.add_post( web.get('/addons/{addon}/changelog', api_addons.changelog),
'/addons/{addon}/rebuild', api_addons.rebuild) web.post('/addons/{addon}/stdin', api_addons.stdin),
self.webapp.router.add_get('/addons/{addon}/logs', api_addons.logs) web.get('/addons/{addon}/stats', api_addons.stats),
self.webapp.router.add_get('/addons/{addon}/icon', api_addons.icon) ])
self.webapp.router.add_get('/addons/{addon}/logo', api_addons.logo)
self.webapp.router.add_get(
'/addons/{addon}/changelog', api_addons.changelog)
self.webapp.router.add_post('/addons/{addon}/stdin', api_addons.stdin)
self.webapp.router.add_get('/addons/{addon}/stats', api_addons.stats)
def _register_snapshots(self): def _register_snapshots(self):
"""Register snapshots function.""" """Register snapshots function."""
api_snapshots = APISnapshots() api_snapshots = APISnapshots()
api_snapshots.coresys = self.coresys api_snapshots.coresys = self.coresys
self.webapp.router.add_get('/snapshots', api_snapshots.list) self.webapp.add_routes([
self.webapp.router.add_post('/snapshots/reload', api_snapshots.reload) web.get('/snapshots', api_snapshots.list),
web.post('/snapshots/reload', api_snapshots.reload),
self.webapp.router.add_post( web.post('/snapshots/new/full', api_snapshots.snapshot_full),
'/snapshots/new/full', api_snapshots.snapshot_full) web.post('/snapshots/new/partial', api_snapshots.snapshot_partial),
self.webapp.router.add_post( web.post('/snapshots/new/upload', api_snapshots.upload),
'/snapshots/new/partial', api_snapshots.snapshot_partial) web.get('/snapshots/{snapshot}/info', api_snapshots.info),
self.webapp.router.add_post( web.post('/snapshots/{snapshot}/remove', api_snapshots.remove),
'/snapshots/new/upload', api_snapshots.upload) web.post('/snapshots/{snapshot}/restore/full',
api_snapshots.restore_full),
self.webapp.router.add_get( web.post('/snapshots/{snapshot}/restore/partial',
'/snapshots/{snapshot}/info', api_snapshots.info) api_snapshots.restore_partial),
self.webapp.router.add_post( web.get('/snapshots/{snapshot}/download', api_snapshots.download),
'/snapshots/{snapshot}/remove', api_snapshots.remove) ])
self.webapp.router.add_post(
'/snapshots/{snapshot}/restore/full', api_snapshots.restore_full)
self.webapp.router.add_post(
'/snapshots/{snapshot}/restore/partial',
api_snapshots.restore_partial)
self.webapp.router.add_get(
'/snapshots/{snapshot}/download',
api_snapshots.download)
def _register_services(self): def _register_services(self):
api_services = APIServices() api_services = APIServices()
api_services.coresys = self.coresys api_services.coresys = self.coresys
self.webapp.router.add_get('/services', api_services.list) self.webapp.add_routes([
web.get('/services', api_services.list),
self.webapp.router.add_get( web.get('/services/{service}', api_services.get_service),
'/services/{service}', api_services.get_service) web.post('/services/{service}', api_services.set_service),
self.webapp.router.add_post( web.delete('/services/{service}', api_services.del_service),
'/services/{service}', api_services.set_service) ])
self.webapp.router.add_delete(
'/services/{service}', api_services.del_service)
def _register_discovery(self): def _register_discovery(self):
api_discovery = APIDiscovery() api_discovery = APIDiscovery()
api_discovery.coresys = self.coresys api_discovery.coresys = self.coresys
self.webapp.router.add_get( self.webapp.add_routes([
'/services/discovery', api_discovery.list) web.get('/services/discovery', api_discovery.list),
self.webapp.router.add_get( web.get('/services/discovery/{uuid}', api_discovery.get_discovery),
'/services/discovery/{uuid}', api_discovery.get_discovery) web.delete('/services/discovery/{uuid}',
self.webapp.router.add_delete( api_discovery.del_discovery),
'/services/discovery/{uuid}', api_discovery.del_discovery) web.post('/services/discovery', api_discovery.set_discovery),
self.webapp.router.add_post( ])
'/services/discovery', api_discovery.set_discovery)
def _register_panel(self): def _register_panel(self):
"""Register panel for homeassistant.""" """Register panel for homeassistant."""
def create_panel_response(build_type): def create_response(build_type):
"""Create a function to generate a response.""" """Create a function to generate a response."""
path = Path(__file__).parent.joinpath( path = Path(__file__).parent.joinpath(
f"panel/{build_type}.html") f"panel/{build_type}.html")
return lambda request: web.FileResponse(path) return lambda request: web.FileResponse(path)
# This route is for backwards compatibility with HA < 0.58 # This route is for backwards compatibility with HA < 0.58
self.webapp.router.add_get( self.webapp.add_routes([
'/panel', create_panel_response('hassio-main-es5')) web.get('/panel', create_response('hassio-main-es5'))])
# This route is for backwards compatibility with HA 0.58 - 0.61 # This route is for backwards compatibility with HA 0.58 - 0.61
self.webapp.router.add_get( self.webapp.add_routes([
'/panel_es5', create_panel_response('hassio-main-es5')) web.get('/panel_es5', create_response('hassio-main-es5')),
self.webapp.router.add_get( web.get('/panel_latest', create_response('hassio-main-latest')),
'/panel_latest', create_panel_response('hassio-main-latest')) ])
# This route is for HA > 0.61 # This route is for HA > 0.61
self.webapp.router.add_get( self.webapp.add_routes([
'/app-es5/index.html', create_panel_response('index')) web.get('/app-es5/index.html', create_response('index')),
self.webapp.router.add_get( web.get('/app-es5/hassio-app.html', create_response('hassio-app')),
'/app-es5/hassio-app.html', create_panel_response('hassio-app')) ])
async def start(self): async def start(self):
"""Run rest api webserver.""" """Run rest api webserver."""

View File

@@ -17,7 +17,7 @@ from ..const import (
ATTR_CHANGELOG, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_LONG_DESCRIPTION, ATTR_CHANGELOG, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_LONG_DESCRIPTION,
ATTR_CPU_PERCENT, ATTR_MEMORY_LIMIT, ATTR_MEMORY_USAGE, ATTR_NETWORK_TX, ATTR_CPU_PERCENT, ATTR_MEMORY_LIMIT, ATTR_MEMORY_USAGE, ATTR_NETWORK_TX,
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES, ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES,
ATTR_DISCOVERY, ATTR_DISCOVERY, ATTR_SECCOMP, ATTR_APPARMOR,
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT) CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..validate import DOCKER_PORTS from ..validate import DOCKER_PORTS
@@ -123,6 +123,8 @@ class APIAddons(CoreSysAttributes):
ATTR_HOST_IPC: addon.host_ipc, ATTR_HOST_IPC: addon.host_ipc,
ATTR_HOST_DBUS: addon.host_dbus, ATTR_HOST_DBUS: addon.host_dbus,
ATTR_PRIVILEGED: addon.privileged, ATTR_PRIVILEGED: addon.privileged,
ATTR_SECCOMP: addon.seccomp,
ATTR_APPARMOR: addon.apparmor,
ATTR_DEVICES: self._pretty_devices(addon), ATTR_DEVICES: self._pretty_devices(addon),
ATTR_ICON: addon.with_icon, ATTR_ICON: addon.with_icon,
ATTR_LOGO: addon.with_logo, ATTR_LOGO: addon.with_logo,

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@@ -83,7 +83,7 @@ class APIProxy(CoreSysAttributes):
if not data: if not data:
await response.write_eof() await response.write_eof()
break break
response.write(data) await response.write(data)
except aiohttp.ClientError: except aiohttp.ClientError:
await response.write_eof() await response.write_eof()

View File

@@ -6,6 +6,7 @@ from aiohttp.web import middleware
from aiohttp.web_exceptions import HTTPUnauthorized from aiohttp.web_exceptions import HTTPUnauthorized
from ..const import HEADER_TOKEN, REQUEST_FROM from ..const import HEADER_TOKEN, REQUEST_FROM
from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -16,35 +17,41 @@ NO_SECURITY_CHECK = set((
)) ))
@middleware class SecurityMiddleware(CoreSysAttributes):
async def security_layer(request, handler): """Security middleware functions."""
"""Check security access of this layer."""
coresys = request.app['coresys']
hassio_token = request.headers.get(HEADER_TOKEN)
# Ignore security check def __init__(self, coresys):
for rule in NO_SECURITY_CHECK: """Initialize security middleware."""
if rule.match(request.path): self.coresys = coresys
_LOGGER.debug("Passthrough %s", request.path)
@middleware
async def token_validation(self, request, handler):
"""Check security access of this layer."""
hassio_token = request.headers.get(HEADER_TOKEN)
# Ignore security check
for rule in NO_SECURITY_CHECK:
if rule.match(request.path):
_LOGGER.debug("Passthrough %s", request.path)
return await handler(request)
# Need to be removed later
if not hassio_token:
_LOGGER.warning("Invalid token for access %s", request.path)
request[REQUEST_FROM] = 'UNKNOWN'
return await handler(request) return await handler(request)
# Need to be removed later # Home-Assistant
if not hassio_token: if hassio_token == self._homeassistant.uuid:
_LOGGER.warning("Invalid token for access %s", request.path) _LOGGER.debug("%s access from Home-Assistant", request.path)
request[REQUEST_FROM] = 'UNKNOWN' request[REQUEST_FROM] = 'homeassistant'
return await handler(request) return await handler(request)
# Home-Assistant # Add-on
if hassio_token == coresys.homeassistant.uuid: addon = self._addons.from_uuid(hassio_token)
_LOGGER.debug("%s access from Home-Assistant", request.path) if addon:
request[REQUEST_FROM] = 'homeassistant' _LOGGER.info("%s access from %s", request.path, addon.slug)
return await handler(request) request[REQUEST_FROM] = addon.slug
return await handler(request)
# Add-on raise HTTPUnauthorized()
addon = coresys.addons.from_uuid(hassio_token)
if addon:
_LOGGER.info("%s access from %s", request.path, addon.slug)
request[REQUEST_FROM] = addon.slug
return await handler(request)
raise HTTPUnauthorized()

View File

@@ -6,20 +6,19 @@ import voluptuous as vol
from .utils import api_process, api_process_raw, api_validate from .utils import api_process, api_process_raw, api_validate
from ..const import ( from ..const import (
ATTR_ADDONS, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_BETA_CHANNEL, ATTR_ARCH, ATTR_ADDONS, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_CHANNEL, ATTR_ARCH,
HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_LOGO, ATTR_REPOSITORY, HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_LOGO, ATTR_REPOSITORY,
ATTR_DESCRIPTON, ATTR_NAME, ATTR_SLUG, ATTR_INSTALLED, ATTR_TIMEZONE, ATTR_DESCRIPTON, ATTR_NAME, ATTR_SLUG, ATTR_INSTALLED, ATTR_TIMEZONE,
ATTR_STATE, ATTR_WAIT_BOOT, ATTR_CPU_PERCENT, ATTR_MEMORY_USAGE, ATTR_STATE, ATTR_WAIT_BOOT, ATTR_CPU_PERCENT, ATTR_MEMORY_USAGE,
ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_BLK_READ, ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_BLK_READ,
ATTR_BLK_WRITE, CONTENT_TYPE_BINARY, ATTR_ICON) ATTR_BLK_WRITE, CONTENT_TYPE_BINARY, ATTR_ICON)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..validate import validate_timezone, WAIT_BOOT, REPOSITORIES from ..validate import validate_timezone, WAIT_BOOT, REPOSITORIES, CHANNELS
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SCHEMA_OPTIONS = vol.Schema({ SCHEMA_OPTIONS = vol.Schema({
# pylint: disable=no-value-for-parameter vol.Optional(ATTR_CHANNEL): CHANNELS,
vol.Optional(ATTR_BETA_CHANNEL): vol.Boolean(),
vol.Optional(ATTR_ADDONS_REPOSITORIES): REPOSITORIES, vol.Optional(ATTR_ADDONS_REPOSITORIES): REPOSITORIES,
vol.Optional(ATTR_TIMEZONE): validate_timezone, vol.Optional(ATTR_TIMEZONE): validate_timezone,
vol.Optional(ATTR_WAIT_BOOT): WAIT_BOOT, vol.Optional(ATTR_WAIT_BOOT): WAIT_BOOT,
@@ -59,7 +58,7 @@ class APISupervisor(CoreSysAttributes):
return { return {
ATTR_VERSION: HASSIO_VERSION, ATTR_VERSION: HASSIO_VERSION,
ATTR_LAST_VERSION: self._updater.version_hassio, ATTR_LAST_VERSION: self._updater.version_hassio,
ATTR_BETA_CHANNEL: self._updater.beta_channel, ATTR_CHANNEL: self._updater.channel,
ATTR_ARCH: self._arch, ATTR_ARCH: self._arch,
ATTR_WAIT_BOOT: self._config.wait_boot, ATTR_WAIT_BOOT: self._config.wait_boot,
ATTR_TIMEZONE: self._config.timezone, ATTR_TIMEZONE: self._config.timezone,
@@ -72,8 +71,8 @@ class APISupervisor(CoreSysAttributes):
"""Set supervisor options.""" """Set supervisor options."""
body = await api_validate(SCHEMA_OPTIONS, request) body = await api_validate(SCHEMA_OPTIONS, request)
if ATTR_BETA_CHANNEL in body: if ATTR_CHANNEL in body:
self._updater.beta_channel = body[ATTR_BETA_CHANNEL] self._updater.channel = body[ATTR_CHANNEL]
if ATTR_TIMEZONE in body: if ATTR_TIMEZONE in body:
self._config.timezone = body[ATTR_TIMEZONE] self._config.timezone = body[ATTR_TIMEZONE]

View File

@@ -2,7 +2,7 @@
from pathlib import Path from pathlib import Path
from ipaddress import ip_network from ipaddress import ip_network
HASSIO_VERSION = '0.93' HASSIO_VERSION = '0.100'
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/' URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
'hassio/{}/version.json') 'hassio/{}/version.json')
@@ -70,7 +70,7 @@ ATTR_VERSION = 'version'
ATTR_AUTO_UART = 'auto_uart' ATTR_AUTO_UART = 'auto_uart'
ATTR_LAST_BOOT = 'last_boot' ATTR_LAST_BOOT = 'last_boot'
ATTR_LAST_VERSION = 'last_version' ATTR_LAST_VERSION = 'last_version'
ATTR_BETA_CHANNEL = 'beta_channel' ATTR_CHANNEL = 'channel'
ATTR_NAME = 'name' ATTR_NAME = 'name'
ATTR_SLUG = 'slug' ATTR_SLUG = 'slug'
ATTR_DESCRIPTON = 'description' ATTR_DESCRIPTON = 'description'
@@ -158,6 +158,9 @@ ATTR_SERVICES = 'services'
ATTR_DISCOVERY = 'discovery' ATTR_DISCOVERY = 'discovery'
ATTR_PROTECTED = 'protected' ATTR_PROTECTED = 'protected'
ATTR_CRYPTO = 'crypto' ATTR_CRYPTO = 'crypto'
ATTR_BRANCH = 'branch'
ATTR_SECCOMP = 'seccomp'
ATTR_APPARMOR = 'apparmor'
SERVICE_MQTT = 'mqtt' SERVICE_MQTT = 'mqtt'
@@ -185,6 +188,10 @@ ARCH_AARCH64 = 'aarch64'
ARCH_AMD64 = 'amd64' ARCH_AMD64 = 'amd64'
ARCH_I386 = 'i386' ARCH_I386 = 'i386'
CHANNEL_STABLE = 'stable'
CHANNEL_BETA = 'beta'
CHANNEL_DEV = 'dev'
REPOSITORY_CORE = 'core' REPOSITORY_CORE = 'core'
REPOSITORY_LOCAL = 'local' REPOSITORY_LOCAL = 'local'
@@ -197,3 +204,7 @@ SNAPSHOT_FULL = 'full'
SNAPSHOT_PARTIAL = 'partial' SNAPSHOT_PARTIAL = 'partial'
CRYPTO_AES128 = 'aes128' CRYPTO_AES128 = 'aes128'
SECURITY_PROFILE = 'profile'
SECURITY_DEFAULT = 'default'
SECURITY_DISABLE = 'disable'

View File

@@ -56,12 +56,12 @@ class HassIO(CoreSysAttributes):
async def start(self): async def start(self):
"""Start HassIO orchestration.""" """Start HassIO orchestration."""
# on release channel, try update itself # on release channel, try update itself
# on beta channel, only read new versions # on dev mode, only read new versions
if not self._updater.beta_channel and self._supervisor.need_update: if not self._dev and self._supervisor.need_update:
if await self._supervisor.update(): if await self._supervisor.update():
return return
else: else:
_LOGGER.info("Ignore Hass.io auto updates on beta mode") _LOGGER.info("Ignore Hass.io auto updates on dev channel")
# start api # start api
await self._api.start() await self._api.start()
@@ -108,10 +108,10 @@ class HassIO(CoreSysAttributes):
# don't process scheduler anymore # don't process scheduler anymore
self._scheduler.suspend = True self._scheduler.suspend = True
# process stop tasks
self._websession.close()
self._websession_ssl.close()
# process async stop tasks # process async stop tasks
await asyncio.wait( await asyncio.wait([
[self._api.stop(), self._dns.stop()], loop=self._loop) self._api.stop(),
self._dns.stop(),
self._websession.close(),
self._websession_ssl.close()
], loop=self._loop)

View File

@@ -2,6 +2,7 @@
import aiohttp import aiohttp
from .const import CHANNEL_DEV
from .config import CoreConfig from .config import CoreConfig
from .docker import DockerAPI from .docker import DockerAPI
from .misc.dns import DNSForward from .misc.dns import DNSForward
@@ -49,6 +50,11 @@ class CoreSys(object):
return self._supervisor.arch return self._supervisor.arch
return None return None
@property
def dev(self):
"""Return True if we run dev modus."""
return self._updater.channel == CHANNEL_DEV
@property @property
def loop(self): def loop(self):
"""Return loop object.""" """Return loop object."""

View File

@@ -6,11 +6,11 @@ import docker
import requests import requests
from .interface import DockerInterface from .interface import DockerInterface
from .utils import docker_process
from ..addons.build import AddonBuild from ..addons.build import AddonBuild
from ..const import ( from ..const import (
MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN, MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN,
ENV_TIME) ENV_TIME, SECURITY_PROFILE, SECURITY_DISABLE)
from ..utils import process_lock
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -121,14 +121,21 @@ class DockerAddon(DockerInterface):
@property @property
def security_opt(self): def security_opt(self):
"""Controlling security opt.""" """Controlling security opt."""
privileged = self.addon.privileged or [] security = []
# Disable AppArmor sinse it make troubles wit SYS_ADMIN # AppArmor
if 'SYS_ADMIN' in privileged: if self.addon.apparmor == SECURITY_DISABLE:
return [ security.append("apparmor:unconfined")
"apparmor:unconfined", elif self.addon.apparmor == SECURITY_PROFILE:
] security.append(f"apparmor={self.addon.slug}")
return None
# Seccomp
if self.addon.seccomp == SECURITY_DISABLE:
security.append("seccomp=unconfined")
elif self.addon.seccomp == SECURITY_PROFILE:
security.append(f"seccomp={self.addon.path_seccomp}")
return security or None
@property @property
def tmpfs(self): def tmpfs(self):
@@ -285,7 +292,7 @@ class DockerAddon(DockerInterface):
_LOGGER.info("Build %s:%s done", self.image, tag) _LOGGER.info("Build %s:%s done", self.image, tag)
return True return True
@docker_process @process_lock
def export_image(self, path): def export_image(self, path):
"""Export current images into a tar file.""" """Export current images into a tar file."""
return self._loop.run_in_executor(None, self._export_image, path) return self._loop.run_in_executor(None, self._export_image, path)
@@ -313,7 +320,7 @@ class DockerAddon(DockerInterface):
_LOGGER.info("Export image %s done", self.image) _LOGGER.info("Export image %s done", self.image)
return True return True
@docker_process @process_lock
def import_image(self, path, tag): def import_image(self, path, tag):
"""Import a tar file as image.""" """Import a tar file as image."""
return self._loop.run_in_executor(None, self._import_image, path, tag) return self._loop.run_in_executor(None, self._import_image, path, tag)
@@ -338,7 +345,7 @@ class DockerAddon(DockerInterface):
self._cleanup() self._cleanup()
return True return True
@docker_process @process_lock
def write_stdin(self, data): def write_stdin(self, data):
"""Write to add-on stdin.""" """Write to add-on stdin."""
return self._loop.run_in_executor(None, self._write_stdin, data) return self._loop.run_in_executor(None, self._write_stdin, data)

View File

@@ -5,10 +5,10 @@ import logging
import docker import docker
from .utils import docker_process
from .stats import DockerStats from .stats import DockerStats
from ..const import LABEL_VERSION, LABEL_ARCH from ..const import LABEL_VERSION, LABEL_ARCH
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..utils import process_lock
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -20,7 +20,7 @@ class DockerInterface(CoreSysAttributes):
"""Initialize docker base wrapper.""" """Initialize docker base wrapper."""
self.coresys = coresys self.coresys = coresys
self._meta = None self._meta = None
self.lock = asyncio.Lock(loop=self._loop) self.lock = asyncio.Lock(loop=coresys.loop)
@property @property
def timeout(self): def timeout(self):
@@ -58,7 +58,7 @@ class DockerInterface(CoreSysAttributes):
"""Return True if a task is in progress.""" """Return True if a task is in progress."""
return self.lock.locked() return self.lock.locked()
@docker_process @process_lock
def install(self, tag): def install(self, tag):
"""Pull docker image.""" """Pull docker image."""
return self._loop.run_in_executor(None, self._install, tag) return self._loop.run_in_executor(None, self._install, tag)
@@ -126,7 +126,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def attach(self): def attach(self):
"""Attach to running docker container.""" """Attach to running docker container."""
return self._loop.run_in_executor(None, self._attach) return self._loop.run_in_executor(None, self._attach)
@@ -149,7 +149,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def run(self): def run(self):
"""Run docker image.""" """Run docker image."""
return self._loop.run_in_executor(None, self._run) return self._loop.run_in_executor(None, self._run)
@@ -161,7 +161,7 @@ class DockerInterface(CoreSysAttributes):
""" """
raise NotImplementedError() raise NotImplementedError()
@docker_process @process_lock
def stop(self): def stop(self):
"""Stop/remove docker container.""" """Stop/remove docker container."""
return self._loop.run_in_executor(None, self._stop) return self._loop.run_in_executor(None, self._stop)
@@ -187,7 +187,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def remove(self): def remove(self):
"""Remove docker images.""" """Remove docker images."""
return self._loop.run_in_executor(None, self._remove) return self._loop.run_in_executor(None, self._remove)
@@ -219,7 +219,7 @@ class DockerInterface(CoreSysAttributes):
self._meta = None self._meta = None
return True return True
@docker_process @process_lock
def update(self, tag): def update(self, tag):
"""Update a docker image.""" """Update a docker image."""
return self._loop.run_in_executor(None, self._update, tag) return self._loop.run_in_executor(None, self._update, tag)
@@ -264,32 +264,7 @@ class DockerInterface(CoreSysAttributes):
except docker.errors.DockerException as err: except docker.errors.DockerException as err:
_LOGGER.warning("Can't grap logs from %s: %s", self.image, err) _LOGGER.warning("Can't grap logs from %s: %s", self.image, err)
@docker_process @process_lock
def restart(self):
"""Restart docker container."""
return self._loop.run_in_executor(None, self._restart)
def _restart(self):
"""Restart docker container.
Need run inside executor.
"""
try:
container = self._docker.containers.get(self.name)
except docker.errors.DockerException:
return False
_LOGGER.info("Restart %s", self.image)
try:
container.restart(timeout=self.timeout)
except docker.errors.DockerException as err:
_LOGGER.warning("Can't restart %s: %s", self.image, err)
return False
return True
@docker_process
def cleanup(self): def cleanup(self):
"""Check if old version exists and cleanup.""" """Check if old version exists and cleanup."""
return self._loop.run_in_executor(None, self._cleanup) return self._loop.run_in_executor(None, self._cleanup)
@@ -315,7 +290,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def execute_command(self, command): def execute_command(self, command):
"""Create a temporary container and run command.""" """Create a temporary container and run command."""
return self._loop.run_in_executor(None, self._execute_command, command) return self._loop.run_in_executor(None, self._execute_command, command)

View File

@@ -1,20 +0,0 @@
"""HassIO docker utilitys."""
import logging
_LOGGER = logging.getLogger(__name__)
# pylint: disable=protected-access
def docker_process(method):
"""Wrap function with only run once."""
async def wrap_api(api, *args, **kwargs):
"""Return api wrapper."""
if api.lock.locked():
_LOGGER.error(
"Can't excute %s while a task is in progress", method.__name__)
return False
async with api.lock:
return await method(api, *args, **kwargs)
return wrap_api

View File

@@ -16,7 +16,7 @@ from .const import (
ATTR_WAIT_BOOT, HEADER_HA_ACCESS, CONTENT_TYPE_JSON) ATTR_WAIT_BOOT, HEADER_HA_ACCESS, CONTENT_TYPE_JSON)
from .coresys import CoreSysAttributes from .coresys import CoreSysAttributes
from .docker.homeassistant import DockerHomeAssistant from .docker.homeassistant import DockerHomeAssistant
from .utils import convert_to_ascii from .utils import convert_to_ascii, process_lock
from .utils.json import JsonConfig from .utils.json import JsonConfig
from .validate import SCHEMA_HASS_CONFIG from .validate import SCHEMA_HASS_CONFIG
@@ -35,6 +35,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG) super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
self.coresys = coresys self.coresys = coresys
self.instance = DockerHomeAssistant(coresys) self.instance = DockerHomeAssistant(coresys)
self.lock = asyncio.Lock(loop=coresys.loop)
async def load(self): async def load(self):
"""Prepare HomeAssistant object.""" """Prepare HomeAssistant object."""
@@ -162,6 +163,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
"""Return a UUID of this HomeAssistant.""" """Return a UUID of this HomeAssistant."""
return self._data[ATTR_UUID] return self._data[ATTR_UUID]
@process_lock
async def install_landingpage(self): async def install_landingpage(self):
"""Install a landingpage.""" """Install a landingpage."""
_LOGGER.info("Setup HomeAssistant landingpage") _LOGGER.info("Setup HomeAssistant landingpage")
@@ -172,8 +174,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
await asyncio.sleep(60, loop=self._loop) await asyncio.sleep(60, loop=self._loop)
# Run landingpage after installation # Run landingpage after installation
await self.start() await self._start()
@process_lock
async def install(self): async def install(self):
"""Install a landingpage.""" """Install a landingpage."""
_LOGGER.info("Setup HomeAssistant") _LOGGER.info("Setup HomeAssistant")
@@ -191,9 +194,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
# finishing # finishing
_LOGGER.info("HomeAssistant docker now installed") _LOGGER.info("HomeAssistant docker now installed")
if self.boot: if self.boot:
await self.start() await self._start()
await self.instance.cleanup() await self.instance.cleanup()
@process_lock
async def update(self, version=None): async def update(self, version=None):
"""Update HomeAssistant version.""" """Update HomeAssistant version."""
version = version or self.last_version version = version or self.last_version
@@ -208,15 +212,23 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
return await self.instance.update(version) return await self.instance.update(version)
finally: finally:
if running: if running:
await self.start() await self._start()
async def start(self): async def _start(self):
"""Run HomeAssistant docker.""" """Start HomeAssistant docker & wait."""
if not await self.instance.run(): if not await self.instance.run():
return False return False
return await self._block_till_run() return await self._block_till_run()
@process_lock
def start(self):
"""Run HomeAssistant docker.
Return a coroutine.
"""
return self._start()
@process_lock
def stop(self): def stop(self):
"""Stop HomeAssistant docker. """Stop HomeAssistant docker.
@@ -224,12 +236,11 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
""" """
return self.instance.stop() return self.instance.stop()
@process_lock
async def restart(self): async def restart(self):
"""Restart HomeAssistant docker.""" """Restart HomeAssistant docker."""
if not await self.instance.restart(): await self.instance.stop()
return False return await self._start()
return await self._block_till_run()
def logs(self): def logs(self):
"""Get HomeAssistant docker logs. """Get HomeAssistant docker logs.
@@ -262,7 +273,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@property @property
def in_progress(self): def in_progress(self):
"""Return True if a task is in progress.""" """Return True if a task is in progress."""
return self.instance.in_progress return self.instance.in_progress or self.lock.locked()
async def check_config(self): async def check_config(self):
"""Run homeassistant config check.""" """Run homeassistant config check."""

View File

@@ -173,14 +173,17 @@ class SnapshotManager(CoreSysAttributes):
if addon and addon.is_installed: if addon and addon.is_installed:
addon_list.append(addon) addon_list.append(addon)
continue continue
_LOGGER.warning("Add-on %s not found", addon_slug) _LOGGER.warning(
"Add-on %s not found/installed", addon_slug)
_LOGGER.info("Snapshot %s store Add-ons", snapshot.slug) if addon_list:
await snapshot.store_addons(addon_list) _LOGGER.info("Snapshot %s store Add-ons", snapshot.slug)
await snapshot.store_addons(addon_list)
# snapshot folders # Snapshot folders
_LOGGER.info("Snapshot %s store folders", snapshot.slug) if folders:
await snapshot.store_folders(folders) _LOGGER.info("Snapshot %s store folders", snapshot.slug)
await snapshot.store_folders(folders)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Snapshot %s error", snapshot.slug) _LOGGER.exception("Snapshot %s error", snapshot.slug)

View File

@@ -151,7 +151,7 @@ class Snapshot(CoreSysAttributes):
def _encrypt_data(self, data): def _encrypt_data(self, data):
"""Make data secure.""" """Make data secure."""
if not self._key: if not self._key or data is None:
return data return data
return b64encode( return b64encode(
@@ -159,7 +159,7 @@ class Snapshot(CoreSysAttributes):
def _decrypt_data(self, data): def _decrypt_data(self, data):
"""Make data readable.""" """Make data readable."""
if not self._key: if not self._key or data is None:
return data return data
return Padding.unpad( return Padding.unpad(

View File

@@ -78,8 +78,8 @@ class Tasks(CoreSysAttributes):
return return
# don't perform a update on beta/dev channel # don't perform a update on beta/dev channel
if self._updater.beta_channel: if self._dev:
_LOGGER.warning("Ignore Hass.io update on beta upstream!") _LOGGER.warning("Ignore Hass.io update on dev channel!")
return return
_LOGGER.info("Found new Hass.io version") _LOGGER.info("Found new Hass.io version")

View File

@@ -9,7 +9,7 @@ import async_timeout
from .const import ( from .const import (
URL_HASSIO_VERSION, FILE_HASSIO_UPDATER, ATTR_HOMEASSISTANT, ATTR_HASSIO, URL_HASSIO_VERSION, FILE_HASSIO_UPDATER, ATTR_HOMEASSISTANT, ATTR_HASSIO,
ATTR_BETA_CHANNEL) ATTR_CHANNEL, CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV)
from .coresys import CoreSysAttributes from .coresys import CoreSysAttributes
from .utils import AsyncThrottle from .utils import AsyncThrottle
from .utils.json import JsonConfig from .utils.json import JsonConfig
@@ -17,6 +17,12 @@ from .validate import SCHEMA_UPDATER_CONFIG
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CHANNEL_TO_BRANCH = {
CHANNEL_STABLE: 'master',
CHANNEL_BETA: 'rc',
CHANNEL_DEV: 'dev',
}
class Updater(JsonConfig, CoreSysAttributes): class Updater(JsonConfig, CoreSysAttributes):
"""Fetch last versions from version.json.""" """Fetch last versions from version.json."""
@@ -44,21 +50,14 @@ class Updater(JsonConfig, CoreSysAttributes):
return self._data.get(ATTR_HASSIO) return self._data.get(ATTR_HASSIO)
@property @property
def upstream(self): def channel(self):
"""Return Upstream branch for version.""" """Return upstream channel of hassio instance."""
if self.beta_channel: return self._data[ATTR_CHANNEL]
return 'dev'
return 'master'
@property @channel.setter
def beta_channel(self): def channel(self, value):
"""Return True if we run in beta upstream.""" """Set upstream mode."""
return self._data[ATTR_BETA_CHANNEL] self._data[ATTR_CHANNEL] = value
@beta_channel.setter
def beta_channel(self, value):
"""Set beta upstream mode."""
self._data[ATTR_BETA_CHANNEL] = bool(value)
@AsyncThrottle(timedelta(seconds=60)) @AsyncThrottle(timedelta(seconds=60))
async def reload(self): async def reload(self):
@@ -66,7 +65,7 @@ class Updater(JsonConfig, CoreSysAttributes):
Is a coroutine. Is a coroutine.
""" """
url = URL_HASSIO_VERSION.format(self.upstream) url = URL_HASSIO_VERSION.format(CHANNEL_TO_BRANCH[self.channel])
try: try:
_LOGGER.info("Fetch update data from %s", url) _LOGGER.info("Fetch update data from %s", url)
with async_timeout.timeout(10, loop=self._loop): with async_timeout.timeout(10, loop=self._loop):

View File

@@ -1,7 +1,9 @@
"""Tools file for HassIO.""" """Tools file for HassIO."""
from datetime import datetime from datetime import datetime
import logging
import re import re
_LOGGER = logging.getLogger(__name__)
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))") RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
@@ -10,6 +12,21 @@ def convert_to_ascii(raw):
return RE_STRING.sub("", raw.decode()) return RE_STRING.sub("", raw.decode())
def process_lock(method):
"""Wrap function with only run once."""
async def wrap_api(api, *args, **kwargs):
"""Return api wrapper."""
if api.lock.locked():
_LOGGER.error(
"Can't excute %s while a task is in progress", method.__name__)
return False
async with api.lock:
return await method(api, *args, **kwargs)
return wrap_api
class AsyncThrottle(object): class AsyncThrottle(object):
""" """
Decorator that prevents a function from being called more than once every Decorator that prevents a function from being called more than once every

View File

@@ -12,7 +12,7 @@ UTC = pytz.utc
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
FREEGEOIP_URL = "https://freegeoip.io/json/" FREEGEOIP_URL = "https://freegeoip.net/json/"
# Copyright (c) Django Software Foundation and individual contributors. # Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved. # All rights reserved.

View File

@@ -1,23 +1,42 @@
"""Validate functions.""" """Validate functions."""
import uuid import uuid
import re
import voluptuous as vol import voluptuous as vol
import pytz import pytz
from .const import ( from .const import (
ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_BETA_CHANNEL, ATTR_TIMEZONE, ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_CHANNEL, ATTR_TIMEZONE,
ATTR_ADDONS_CUSTOM_LIST, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT, ATTR_ADDONS_CUSTOM_LIST, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT,
ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO, ATTR_BOOT, ATTR_LAST_BOOT, ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO, ATTR_BOOT, ATTR_LAST_BOOT,
ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_UUID) ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_UUID,
CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV)
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
ALSA_CHANNEL = vol.Match(r"\d+,\d+") ALSA_CHANNEL = vol.Match(r"\d+,\d+")
WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60)) WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60))
DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$") DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$")
CHANNELS = vol.In([CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV])
def validate_repository(repository):
"""Validate a valide repository."""
data = RE_REPOSITORY.match(repository)
if not data:
raise vol.Invalid("No valid repository format!")
# Validate URL
# pylint: disable=no-value-for-parameter
vol.Url()(data.group('url'))
return repository
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
REPOSITORIES = vol.All([vol.Url()], vol.Unique()) REPOSITORIES = vol.All([validate_repository], vol.Unique())
def validate_timezone(timezone): def validate_timezone(timezone):
@@ -77,9 +96,8 @@ SCHEMA_HASS_CONFIG = vol.Schema({
}, extra=vol.REMOVE_EXTRA) }, extra=vol.REMOVE_EXTRA)
# pylint: disable=no-value-for-parameter
SCHEMA_UPDATER_CONFIG = vol.Schema({ SCHEMA_UPDATER_CONFIG = vol.Schema({
vol.Optional(ATTR_BETA_CHANNEL, default=False): vol.Boolean(), vol.Optional(ATTR_CHANNEL, default=CHANNEL_STABLE): CHANNELS,
vol.Optional(ATTR_HOMEASSISTANT): vol.Coerce(str), vol.Optional(ATTR_HOMEASSISTANT): vol.Coerce(str),
vol.Optional(ATTR_HASSIO): vol.Coerce(str), vol.Optional(ATTR_HASSIO): vol.Coerce(str),
}, extra=vol.REMOVE_EXTRA) }, extra=vol.REMOVE_EXTRA)

View File

@@ -40,9 +40,9 @@ setup(
], ],
include_package_data=True, include_package_data=True,
install_requires=[ install_requires=[
'async_timeout==2.0.0', 'async_timeout==2.0.1',
'aiohttp==2.3.10', 'aiohttp==3.1.2',
'docker==3.1.0', 'docker==3.2.0',
'colorlog==3.1.2', 'colorlog==3.1.2',
'voluptuous==0.11.1', 'voluptuous==0.11.1',
'gitpython==2.1.8', 'gitpython==2.1.8',

View File

@@ -1,7 +1,7 @@
{ {
"hassio": "0.93", "hassio": "0.100",
"homeassistant": "0.63.3", "homeassistant": "0.66.1",
"resinos": "1.1", "resinos": "1.3",
"resinhup": "0.3", "resinhup": "0.3",
"generic": "0.3", "generic": "0.3",
"cluster": "0.1" "cluster": "0.1"