Compare commits

...

28 Commits
0.11 ... 0.16

Author SHA1 Message Date
Pascal Vizeli
b5b68c5c42 Release 0.16
Release 0.16
2017-04-26 22:38:16 +02:00
Pascal Vizeli
d58e847978 Merge remote-tracking branch 'origin/master' into dev 2017-04-26 22:19:08 +02:00
Pascal Vizeli
aad9ae6997 Add OS attribute for hostcontrol (#16)
* Add OS attribute for hostcontrol

* fix lint
2017-04-26 22:14:58 +02:00
Pascal Vizeli
139cf4fae4 Update path (#15) 2017-04-26 22:08:49 +02:00
Pascal Vizeli
e01b2da223 Cleanup 2017-04-26 21:49:29 +02:00
Pascal Vizeli
cbbe2d2d3c Cleanup 2017-04-26 21:48:27 +02:00
Pascal Vizeli
7ca11a96b9 Pump version 2017-04-26 21:47:04 +02:00
Pascal Vizeli
3443d6d715 Update API.md 2017-04-26 17:13:16 +02:00
Pascal Vizeli
99730734a0 update generic HostControll v0.2 2017-04-26 12:59:19 +02:00
Pascal Vizeli
20fcd28dbe Update version.json 2017-04-26 11:27:29 +02:00
Pascal Vizeli
76cead72e8 Update API for hass api v2 (#14)
* Update API for hass api v2

* fix lint

* Refactory the old version of host_control

* cleanup

* Cleanup name inside addons/data

* Cleanup name inside addons/data p2

* Rename api list

* Fix path bug

* Fix wrong config set
2017-04-26 11:15:56 +02:00
Pascal Vizeli
a0f17ffd1d Update version_beta.json 2017-04-26 10:38:20 +02:00
Pascal Vizeli
86d92bdfa2 Update version.json 2017-04-26 10:37:56 +02:00
pvizeli
25a0bc6549 Start rename options inside version 2017-04-26 08:19:16 +02:00
Pascal Vizeli
96971e7054 Update version.json 2017-04-26 00:21:22 +02:00
Pascal Vizeli
2729877fbf Update version_beta.json 2017-04-26 00:10:36 +02:00
Pascal Vizeli
3b8b44fcb7 Store update version for addons (#13) 2017-04-26 00:07:09 +02:00
Pascal Vizeli
22e3d50203 Update homeassistant image 2017-04-25 21:40:01 +02:00
Pascal Vizeli
2090b336e4 Update hassio 0.13 2017-04-25 18:38:56 +02:00
Pascal Vizeli
a028f11a4a Change handling of addon config (#12)
Add an optional extended description…
2017-04-25 18:36:40 +02:00
Pascal Vizeli
4edd02ca8e Update HassIO 0.13 2017-04-25 17:59:30 +02:00
Pascal Vizeli
85dad8c077 Update README.md 2017-04-25 14:21:30 +02:00
Pascal Vizeli
fff2ac3c47 Update HassIO to v0.12 2017-04-24 12:11:21 +02:00
Pascal Vizeli
f99c6335c0 Extend API to view logs from docker (#11)
* Extend API to view logs from docker

* Pump version

* Fix lint

* Change to raw api output

* Fix aiohttp response

* Fix aiohttp response p2

* Fix body convert

* Add attach to docker addon
2017-04-24 12:00:44 +02:00
Pascal Vizeli
98f0ff2a01 Update to hassio 0.12 2017-04-24 10:34:34 +02:00
Pascal Vizeli
43c5fcd159 Update README.md 2017-04-24 00:56:14 +02:00
Pascal Vizeli
784b57622c fix name 2017-04-23 23:27:15 +02:00
Pascal Vizeli
d8d17998fc add generic update function 2017-04-23 21:59:02 +02:00
20 changed files with 411 additions and 240 deletions

55
API.md
View File

@@ -2,7 +2,7 @@
## HassIO REST API ## HassIO REST API
Interface for HomeAssistant to controll things from supervisor. Interface for HomeAssistant to control things from supervisor.
On error: On error:
```json ```json
@@ -29,14 +29,15 @@ On success
```json ```json
{ {
"version": "INSTALL_VERSION", "version": "INSTALL_VERSION",
"current": "CURRENT_VERSION", "last_version": "LAST_VERSION",
"beta": "true|false", "beta_channel": "true|false",
"addons": [ "addons": [
{ {
"name": "xy bla", "name": "xy bla",
"slug": "xy", "slug": "xy",
"version": "CURRENT_VERSION", "version": "LAST_VERSION",
"installed": "none|INSTALL_VERSION", "installed": "none|INSTALL_VERSION",
"dedicated": "bool",
"description": "description" "description": "description"
} }
] ]
@@ -54,7 +55,7 @@ Optional:
- `/supervisor/option` - `/supervisor/option`
```json ```json
{ {
"beta": "true|false" "beta_channel": "true|false"
} }
``` ```
@@ -62,6 +63,10 @@ Optional:
Reload addons/version. Reload addons/version.
- `/supervisor/logs`
Output the raw docker log
### Host ### Host
- `/host/shutdown` - `/host/shutdown`
@@ -69,14 +74,15 @@ Reload addons/version.
- `/host/reboot` - `/host/reboot`
- `/host/info` - `/host/info`
See HostControll info command. See HostControl info command.
```json ```json
{ {
"os": "", "type": "",
"version": "", "version": "",
"current": "", "last_version": "",
"level": "", "features": ["shutdown", "reboot", "update", "network_info", "network_control"],
"hostname": "", "hostname": "",
"os": ""
} }
``` ```
@@ -111,7 +117,7 @@ Optional:
```json ```json
{ {
"version": "INSTALL_VERSION", "version": "INSTALL_VERSION",
"current": "CURRENT_VERSION" "last_version": "LAST_VERSION"
} }
``` ```
@@ -123,13 +129,17 @@ Optional:
} }
``` ```
- `/homeassistant/logs`
Output the raw docker log
### REST API addons ### REST API addons
- `/addons/{addon}/info` - `/addons/{addon}/info`
```json ```json
{ {
"version": "VERSION", "version": "VERSION",
"current": "CURRENT_VERSION", "last_version": "LAST_VERSION",
"state": "started|stopped", "state": "started|stopped",
"boot": "auto|manual", "boot": "auto|manual",
"options": {}, "options": {},
@@ -138,7 +148,10 @@ Optional:
- `/addons/{addon}/options` - `/addons/{addon}/options`
```json ```json
{ } {
"boot": "auto|manual",
"options": {},
}
``` ```
- `/addons/{addon}/start` - `/addons/{addon}/start`
@@ -163,14 +176,18 @@ Optional:
} }
``` ```
## Host Controll - `/addons/{addon}/logs`
Output the raw docker log
## Host Control
Communicate over unix socket with a host daemon. Communicate over unix socket with a host daemon.
- commands - commands
``` ```
# info # info
-> {'os', 'version', 'current', 'level', 'hostname'} -> {'type', 'version', 'last_version', 'features', 'hostname'}
# reboot # reboot
# shutdown # shutdown
# host-update [v] # host-update [v]
@@ -184,10 +201,12 @@ Communicate over unix socket with a host daemon.
# network int route xy # network int route xy
``` ```
level: features:
- 1: power functions - shutdown
- 2: host update - reboot
- 4: network functions - update
- network_info
- network_control
Answer: Answer:
``` ```

View File

@@ -1,13 +1,14 @@
# HassIO # HassIO
First private cloud solution for home automation. First private cloud solution for home automation.
It is a docker image (supervisor) they manage HomeAssistant docker and give a interface to controll itself over UI. It have a own eco system with addons to extend the functionality in a easy way. It is a docker image (supervisor) they manage HomeAssistant docker and give a interface to control itself over UI. It have a own eco system with addons to extend the functionality in a easy way.
[HassIO-Addons](https://github.com/pvizeli/hassio-addons) | [HassIO-Build](https://github.com/pvizeli/hassio-build) [HassIO-Addons](https://github.com/pvizeli/hassio-addons) | [HassIO-Build](https://github.com/pvizeli/hassio-build)
**HassIO is at the moment on development and not ready to use productive!**
## Feature in progress ## Feature in progress
- Backup/Restore - Backup/Restore
- Read docker logs and extend to api
- MQTT addon - MQTT addon
- DHCP-Server addon - DHCP-Server addon
@@ -32,8 +33,8 @@ After extracting the archive, flash it to a drive using [Etcher](https://etcher.
## History ## History
- **0.1**: First techpreview with dumy supervisor (ResinOS 2.0.0-RC5) - **0.1**: First techpreview with dumy supervisor (ResinOS 2.0.0-RC5)
- **0.2**: Fix some bugs and update it to HassIO 0.2 - **0.2**: Fix some bugs and update it to HassIO 0.2
- **0.3**: Update HostControll and feature for HassIO 0.3 (ResinOS 2.0.0 / need reflash) - **0.3**: Update HostControl and feature for HassIO 0.3 (ResinOS 2.0.0 / need reflash)
- **0.4**: Update HostControll and bring resinos OTA (resinhub) back (ResinOS 2.0.0-rev3) - **0.4**: Update HostControl and bring resinos OTA (resinhub) back (ResinOS 2.0.0-rev3)
## Configuring the image ## Configuring the image
You can configure the WiFi network that the image should connect to after flashing using [`resin-device-toolbox`](https://resinos.io/docs/raspberrypi3/gettingstarted/#install-resin-device-toolbox). You can configure the WiFi network that the image should connect to after flashing using [`resin-device-toolbox`](https://resinos.io/docs/raspberrypi3/gettingstarted/#install-resin-device-toolbox).
@@ -48,3 +49,8 @@ Read logoutput from supervisor:
journalctl -f -u resin-supervisor.service journalctl -f -u resin-supervisor.service
docker logs homeassistant docker logs homeassistant
``` ```
## Install on a own System
We have a installer to install HassIO on own linux device without our hardware image:
https://github.com/pvizeli/hassio-build/tree/master/install

View File

@@ -36,6 +36,7 @@ class AddonManager(AddonsData):
for addon in self.list_installed: for addon in self.list_installed:
self.dockers[addon] = DockerAddon( self.dockers[addon] = DockerAddon(
self.config, self.loop, self.dock, self, addon) self.config, self.loop, self.dock, self, addon)
await self.dockers[addon].attach()
async def reload(self): async def reload(self):
"""Update addons from repo and reload list.""" """Update addons from repo and reload list."""
@@ -44,13 +45,8 @@ class AddonManager(AddonsData):
self.read_addons_repo() self.read_addons_repo()
# remove stalled addons # remove stalled addons
tasks = []
for addon in self.list_removed: for addon in self.list_removed:
_LOGGER.info("Old addon %s found") _LOGGER.warning("Dedicated addon '%s' found!", addon)
tasks.append(self.loop.create_task(self.uninstall(addon)))
if tasks:
await asyncio.wait(tasks, loop=self.loop)
async def auto_boot(self, start_type): async def auto_boot(self, start_type):
"""Boot addons with mode auto.""" """Boot addons with mode auto."""
@@ -82,12 +78,12 @@ class AddonManager(AddonsData):
addon_docker = DockerAddon( addon_docker = DockerAddon(
self.config, self.loop, self.dock, self, addon) self.config, self.loop, self.dock, self, addon)
version = version or self.get_version(addon) version = version or self.get_last_version(addon)
if not await addon_docker.install(version): if not await addon_docker.install(version):
return False return False
self.dockers[addon] = addon_docker self.dockers[addon] = addon_docker
self.set_install_addon(addon, version) self.set_addon_install(addon, version)
return True return True
async def uninstall(self, addon): async def uninstall(self, addon):
@@ -109,7 +105,7 @@ class AddonManager(AddonsData):
shutil.rmtree(self.path_data(addon)) shutil.rmtree(self.path_data(addon))
self.dockers.pop(addon) self.dockers.pop(addon)
self.set_uninstall_addon(addon) self.set_addon_uninstall(addon)
return True return True
async def state(self, addon): async def state(self, addon):
@@ -144,16 +140,25 @@ class AddonManager(AddonsData):
async def update(self, addon, version=None): async def update(self, addon, version=None):
"""Update addon.""" """Update addon."""
if not self.is_installed(addon):
_LOGGER.error("Addon %s is not installed", addon)
return False
if addon not in self.dockers: if addon not in self.dockers:
_LOGGER.error("No docker found for addon %s", addon) _LOGGER.error("No docker found for addon %s", addon)
return False return False
version = version or self.get_version(addon) version = version or self.get_last_version(addon)
is_running = self.dockers[addon].is_running()
# update
if await self.dockers[addon].update(version): if await self.dockers[addon].update(version):
self.set_version(addon, version) self.set_addon_update(addon, version)
if is_running:
await self.start(addon)
return True return True
return False return False
async def logs(self, addon):
"""Return addons log output."""
if addon not in self.dockers:
_LOGGER.error("No docker found for addon %s", addon)
return False
return await self.dockers[addon].logs()

View File

@@ -10,13 +10,15 @@ from ..const import (
FILE_HASSIO_ADDONS, ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, FILE_HASSIO_ADDONS, ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON,
ATTR_STARTUP, ATTR_BOOT, ATTR_MAP_SSL, ATTR_MAP_CONFIG, ATTR_OPTIONS, ATTR_STARTUP, ATTR_BOOT, ATTR_MAP_SSL, ATTR_MAP_CONFIG, ATTR_OPTIONS,
ATTR_PORTS, BOOT_AUTO, DOCKER_REPO, ATTR_INSTALLED, ATTR_SCHEMA, ATTR_PORTS, BOOT_AUTO, DOCKER_REPO, ATTR_INSTALLED, ATTR_SCHEMA,
ATTR_IMAGE) ATTR_IMAGE, ATTR_DEDICATED)
from ..config import Config from ..config import Config
from ..tools import read_json_file, write_json_file from ..tools import read_json_file, write_json_file
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ADDONS_REPO_PATTERN = "{}/*/config.json" ADDONS_REPO_PATTERN = "{}/*/config.json"
SYSTEM = "system"
USER = "user"
class AddonsData(Config): class AddonsData(Config):
@@ -26,12 +28,22 @@ class AddonsData(Config):
"""Initialize data holder.""" """Initialize data holder."""
super().__init__(FILE_HASSIO_ADDONS) super().__init__(FILE_HASSIO_ADDONS)
self.config = config self.config = config
self._addons_data = {} self._system_data = self._data.get(SYSTEM, {})
self._user_data = self._data.get(USER, {})
self._current_data = {}
self.arch = None self.arch = None
def save(self):
"""Store data to config file."""
self._data = {
USER: self._user_data,
SYSTEM: self._system_data,
}
super().save()
def read_addons_repo(self): def read_addons_repo(self):
"""Read data from addons repository.""" """Read data from addons repository."""
self._addons_data = {} self._current_data = {}
self._read_addons_folder(self.config.path_addons_repo) self._read_addons_folder(self.config.path_addons_repo)
self._read_addons_folder(self.config.path_addons_custom) self._read_addons_folder(self.config.path_addons_custom)
@@ -45,7 +57,7 @@ class AddonsData(Config):
addon_config = read_json_file(addon) addon_config = read_json_file(addon)
addon_config = SCHEMA_ADDON_CONFIG(addon_config) addon_config = SCHEMA_ADDON_CONFIG(addon_config)
self._addons_data[addon_config[ATTR_SLUG]] = addon_config self._current_data[addon_config[ATTR_SLUG]] = addon_config
except (OSError, KeyError): except (OSError, KeyError):
_LOGGER.warning("Can't read %s", addon) _LOGGER.warning("Can't read %s", addon)
@@ -57,24 +69,25 @@ class AddonsData(Config):
@property @property
def list_installed(self): def list_installed(self):
"""Return a list of installed addons.""" """Return a list of installed addons."""
return set(self._data.keys()) return set(self._system_data.keys())
@property @property
def list_all(self): def list_api(self):
"""Return a list of available addons.""" """Return a list of available addons for api."""
return set(self._addons_data.keys())
@property
def list(self):
"""Return a list of available addons."""
data = [] data = []
for addon, values in self._addons_data.items(): all_addons = {**self._system_data, **self._current_data}
dedicated = self.list_removed
for addon, values in all_addons.items():
i_version = self._user_data.get(addon, {}).get(ATTR_VERSION)
data.append({ data.append({
ATTR_NAME: values[ATTR_NAME], ATTR_NAME: values[ATTR_NAME],
ATTR_SLUG: values[ATTR_SLUG], ATTR_SLUG: values[ATTR_SLUG],
ATTR_DESCRIPTON: values[ATTR_DESCRIPTON], ATTR_DESCRIPTON: values[ATTR_DESCRIPTON],
ATTR_VERSION: values[ATTR_VERSION], ATTR_VERSION: values[ATTR_VERSION],
ATTR_INSTALLED: self._data.get(addon, {}).get(ATTR_VERSION), ATTR_INSTALLED: i_version,
ATTR_DEDICATED: addon in dedicated,
}) })
return data return data
@@ -82,12 +95,12 @@ class AddonsData(Config):
def list_startup(self, start_type): def list_startup(self, start_type):
"""Get list of installed addon with need start by type.""" """Get list of installed addon with need start by type."""
addon_list = set() addon_list = set()
for addon in self._data.keys(): for addon in self._system_data.keys():
if self.get_boot(addon) != BOOT_AUTO: if self.get_boot(addon) != BOOT_AUTO:
continue continue
try: try:
if self._addons_data[addon][ATTR_STARTUP] == start_type: if self._system_data[addon][ATTR_STARTUP] == start_type:
addon_list.add(addon) addon_list.add(addon)
except KeyError: except KeyError:
_LOGGER.warning("Orphaned addon detect %s", addon) _LOGGER.warning("Orphaned addon detect %s", addon)
@@ -99,106 +112,111 @@ class AddonsData(Config):
def list_removed(self): def list_removed(self):
"""Return local addons they not support from repo.""" """Return local addons they not support from repo."""
addon_list = set() addon_list = set()
for addon in self._data.keys(): for addon in self._system_data.keys():
if addon not in self._addons_data: if addon not in self._current_data:
addon_list.add(addon) addon_list.add(addon)
return addon_list return addon_list
def exists_addon(self, addon): def exists_addon(self, addon):
"""Return True if a addon exists.""" """Return True if a addon exists."""
return addon in self._addons_data return addon in self._current_data or addon in self._system_data
def is_installed(self, addon): def is_installed(self, addon):
"""Return True if a addon is installed.""" """Return True if a addon is installed."""
return addon in self._data return addon in self._system_data
def version_installed(self, addon): def version_installed(self, addon):
"""Return installed version.""" """Return installed version."""
return self._data[addon][ATTR_VERSION] return self._user_data[addon][ATTR_VERSION]
def set_install_addon(self, addon, version): def set_addon_install(self, addon, version):
"""Set addon as installed.""" """Set addon as installed."""
self._data[addon] = { self._system_data[addon] = self._current_data[addon]
self._user_data[addon] = {
ATTR_OPTIONS: {},
ATTR_VERSION: version, ATTR_VERSION: version,
ATTR_OPTIONS: {}
} }
self.save() self.save()
def set_uninstall_addon(self, addon): def set_addon_uninstall(self, addon):
"""Set addon as uninstalled.""" """Set addon as uninstalled."""
self._data.pop(addon, None) self._system_data.pop(addon, None)
self._user_data.pop(addon, None)
self.save()
def set_addon_update(self, addon, version):
"""Update version of addon."""
self._system_data[addon] = self._current_data[addon]
self._user_data[addon][ATTR_VERSION] = version
self.save() self.save()
def set_options(self, addon, options): def set_options(self, addon, options):
"""Store user addon options.""" """Store user addon options."""
self._data[addon][ATTR_OPTIONS] = options self._user_data[addon][ATTR_OPTIONS] = options
self.save() self.save()
def set_version(self, addon, version): def set_boot(self, addon, boot):
"""Update version of addon.""" """Store user boot options."""
self._data[addon][ATTR_VERSION] = version self._user_data[addon][ATTR_BOOT] = boot
self.save() self.save()
def get_options(self, addon): def get_options(self, addon):
"""Return options with local changes.""" """Return options with local changes."""
opt = self._addons_data[addon][ATTR_OPTIONS] return {
if addon in self._data: **self._system_data[addon][ATTR_OPTIONS],
opt.update(self._data[addon][ATTR_OPTIONS]) **self._user_data[addon][ATTR_OPTIONS],
return opt }
def get_boot(self, addon): def get_boot(self, addon):
"""Return boot config with prio local settings.""" """Return boot config with prio local settings."""
if ATTR_BOOT in self._data[addon]: if ATTR_BOOT in self._user_data[addon]:
return self._data[addon][ATTR_BOOT] return self._user_data[addon][ATTR_BOOT]
return self._addons_data[addon][ATTR_BOOT] return self._system_data[addon][ATTR_BOOT]
def get_name(self, addon): def get_name(self, addon):
"""Return name of addon.""" """Return name of addon."""
return self._addons_data[addon][ATTR_NAME] return self._system_data[addon][ATTR_NAME]
def get_description(self, addon): def get_description(self, addon):
"""Return description of addon.""" """Return description of addon."""
return self._addons_data[addon][ATTR_DESCRIPTON] return self._system_data[addon][ATTR_DESCRIPTON]
def get_version(self, addon): def get_last_version(self, addon):
"""Return version of addon.""" """Return version of addon."""
return self._addons_data[addon][ATTR_VERSION] if addon not in self._current_data:
return self.version_installed(addon)
def get_slug(self, addon): return self._current_data[addon][ATTR_VERSION]
"""Return slug of addon."""
return self._addons_data[addon][ATTR_SLUG]
def get_ports(self, addon): def get_ports(self, addon):
"""Return ports of addon.""" """Return ports of addon."""
return self._addons_data[addon].get(ATTR_PORTS) return self._system_data[addon].get(ATTR_PORTS)
def get_image(self, addon): def get_image(self, addon):
"""Return image name of addon.""" """Return image name of addon."""
if ATTR_IMAGE not in self._addons_data[addon]: addon_data = self._system_data.get(addon, self._current_data[addon])
return "{}/{}-addon-{}".format(
DOCKER_REPO, self.arch, self.get_slug(addon))
return self._addons_data[addon][ATTR_IMAGE] if ATTR_IMAGE not in addon_data:
return "{}/{}-addon-{}".format(DOCKER_REPO, self.arch, addon)
return addon_data[ATTR_IMAGE]
def need_config(self, addon): def need_config(self, addon):
"""Return True if config map is needed.""" """Return True if config map is needed."""
return self._addons_data[addon][ATTR_MAP_CONFIG] return self._system_data[addon][ATTR_MAP_CONFIG]
def need_ssl(self, addon): def need_ssl(self, addon):
"""Return True if ssl map is needed.""" """Return True if ssl map is needed."""
return self._addons_data[addon][ATTR_MAP_SSL] return self._system_data[addon][ATTR_MAP_SSL]
def path_data(self, addon): def path_data(self, addon):
"""Return addon data path inside supervisor.""" """Return addon data path inside supervisor."""
return "{}/{}".format( return "{}/{}".format(self.config.path_addons_data, addon)
self.config.path_addons_data, self._addons_data[addon][ATTR_SLUG])
def path_data_docker(self, addon): def path_data_docker(self, addon):
"""Return addon data path external for docker.""" """Return addon data path external for docker."""
return "{}/{}".format(self.config.path_addons_data_docker, return "{}/{}".format(self.config.path_addons_data_docker, addon)
self._addons_data[addon][ATTR_SLUG])
def path_addon_options(self, addon): def path_addon_options(self, addon):
"""Return path to addons options.""" """Return path to addons options."""
@@ -220,7 +238,7 @@ class AddonsData(Config):
def get_schema(self, addon): def get_schema(self, addon):
"""Create a schema for addon options.""" """Create a schema for addon options."""
raw_schema = self._addons_data[addon][ATTR_SCHEMA] raw_schema = self._system_data[addon][ATTR_SCHEMA]
schema = vol.Schema(vol.All(dict, validate_options(raw_schema))) schema = vol.Schema(vol.All(dict, validate_options(raw_schema)))
return schema return schema

View File

@@ -25,26 +25,26 @@ class RestAPI(object):
self._handler = None self._handler = None
self.server = None self.server = None
def register_host(self, host_controll): def register_host(self, host_control):
"""Register hostcontroll function.""" """Register hostcontrol function."""
api_host = APIHost(self.config, self.loop, host_controll) api_host = APIHost(self.config, self.loop, host_control)
self.webapp.router.add_get('/host/info', api_host.info) self.webapp.router.add_get('/host/info', api_host.info)
self.webapp.router.add_get('/host/reboot', api_host.reboot) self.webapp.router.add_get('/host/reboot', api_host.reboot)
self.webapp.router.add_get('/host/shutdown', api_host.shutdown) self.webapp.router.add_get('/host/shutdown', api_host.shutdown)
self.webapp.router.add_get('/host/update', api_host.update) self.webapp.router.add_get('/host/update', api_host.update)
def register_network(self, host_controll): def register_network(self, host_control):
"""Register network function.""" """Register network function."""
api_net = APINetwork(self.config, self.loop, host_controll) api_net = APINetwork(self.config, self.loop, host_control)
self.webapp.router.add_get('/network/info', api_net.info) self.webapp.router.add_get('/network/info', api_net.info)
self.webapp.router.add_get('/network/options', api_net.options) self.webapp.router.add_get('/network/options', api_net.options)
def register_supervisor(self, supervisor, addons): def register_supervisor(self, supervisor, addons, host_control):
"""Register supervisor function.""" """Register supervisor function."""
api_supervisor = APISupervisor( api_supervisor = APISupervisor(
self.config, self.loop, supervisor, addons) self.config, self.loop, supervisor, addons, host_control)
self.webapp.router.add_get('/supervisor/ping', api_supervisor.ping) self.webapp.router.add_get('/supervisor/ping', api_supervisor.ping)
self.webapp.router.add_get('/supervisor/info', api_supervisor.info) self.webapp.router.add_get('/supervisor/info', api_supervisor.info)
@@ -52,6 +52,7 @@ class RestAPI(object):
self.webapp.router.add_get('/supervisor/reload', api_supervisor.reload) self.webapp.router.add_get('/supervisor/reload', api_supervisor.reload)
self.webapp.router.add_get( self.webapp.router.add_get(
'/supervisor/options', api_supervisor.options) '/supervisor/options', api_supervisor.options)
self.webapp.router.add_get('/supervisor/logs', api_supervisor.logs)
def register_homeassistant(self, dock_homeassistant): def register_homeassistant(self, dock_homeassistant):
"""Register homeassistant function.""" """Register homeassistant function."""
@@ -59,6 +60,7 @@ class RestAPI(object):
self.webapp.router.add_get('/homeassistant/info', api_hass.info) self.webapp.router.add_get('/homeassistant/info', api_hass.info)
self.webapp.router.add_get('/homeassistant/update', api_hass.update) self.webapp.router.add_get('/homeassistant/update', api_hass.update)
self.webapp.router.add_get('/homeassistant/logs', api_hass.logs)
def register_addons(self, addons): def register_addons(self, addons):
"""Register homeassistant function.""" """Register homeassistant function."""
@@ -74,6 +76,7 @@ class RestAPI(object):
self.webapp.router.add_get('/addons/{addon}/update', api_addons.update) self.webapp.router.add_get('/addons/{addon}/update', api_addons.update)
self.webapp.router.add_get( self.webapp.router.add_get(
'/addons/{addon}/options', api_addons.options) '/addons/{addon}/options', api_addons.options)
self.webapp.router.add_get('/addons/{addon}/logs', api_addons.logs)
async def start(self): async def start(self):
"""Run rest api webserver.""" """Run rest api webserver."""

View File

@@ -5,10 +5,10 @@ import logging
import voluptuous as vol import voluptuous as vol
from voluptuous.humanize import humanize_error from voluptuous.humanize import humanize_error
from .util import api_process, api_validate from .util import api_process, api_process_raw, api_validate
from ..const import ( from ..const import (
ATTR_VERSION, ATTR_CURRENT, ATTR_STATE, ATTR_BOOT, ATTR_OPTIONS, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_STATE, ATTR_BOOT, ATTR_OPTIONS,
STATE_STOPPED, STATE_STARTED) STATE_STOPPED, STATE_STARTED, BOOT_AUTO, BOOT_MANUAL)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -16,6 +16,10 @@ SCHEMA_VERSION = vol.Schema({
vol.Optional(ATTR_VERSION): vol.Coerce(str), vol.Optional(ATTR_VERSION): vol.Coerce(str),
}) })
SCHEMA_OPTIONS = vol.Schema({
vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL])
})
class APIAddons(object): class APIAddons(object):
"""Handle rest api for addons functions.""" """Handle rest api for addons functions."""
@@ -43,23 +47,31 @@ class APIAddons(object):
"""Return addon information.""" """Return addon information."""
addon = self._extract_addon(request) addon = self._extract_addon(request)
info = { return {
ATTR_VERSION: self.addons.version_installed(addon), ATTR_VERSION: self.addons.version_installed(addon),
ATTR_CURRENT: self.addons.get_version(addon), ATTR_LAST_VERSION: self.addons.get_last_version(addon),
ATTR_STATE: await self.addons.state(addon), ATTR_STATE: await self.addons.state(addon),
ATTR_BOOT: self.addons.get_boot(addon), ATTR_BOOT: self.addons.get_boot(addon),
ATTR_OPTIONS: self.addons.get_options(addon), ATTR_OPTIONS: self.addons.get_options(addon),
} }
return info
@api_process @api_process
async def options(self, request): async def options(self, request):
"""Store user options for addon.""" """Store user options for addon."""
addon = self._extract_addon(request) addon = self._extract_addon(request)
schema = self.addons.get_schema(addon) options_schema = self.addons.get_schema(addon)
addon_schema = SCHEMA_OPTIONS.extend({
vol.Optional(ATTR_OPTIONS): options_schema,
})
body = await api_validate(addon_schema, request)
if ATTR_OPTIONS in body:
self.addons.set_options(addon, body[ATTR_OPTIONS])
if ATTR_BOOT in body:
self.addons.set_boot(addon, body[ATTR_BOOT])
options = await api_validate(schema, request)
self.addons.set_options(addon, options)
return True return True
@api_process @api_process
@@ -68,7 +80,7 @@ class APIAddons(object):
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
addon = self._extract_addon(request, check_installed=False) addon = self._extract_addon(request, check_installed=False)
version = body.get( version = body.get(
ATTR_VERSION, self.addons.get_version(addon)) ATTR_VERSION, self.addons.get_last_version(addon))
return await asyncio.shield( return await asyncio.shield(
self.addons.install(addon, version), loop=self.loop) self.addons.install(addon, version), loop=self.loop)
@@ -117,10 +129,16 @@ class APIAddons(object):
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
addon = self._extract_addon(request) addon = self._extract_addon(request)
version = body.get( version = body.get(
ATTR_VERSION, self.addons.get_version(addon)) ATTR_VERSION, self.addons.get_last_version(addon))
if version == self.addons.version_installed(addon): if version == self.addons.version_installed(addon):
raise RuntimeError("Version is already in use") raise RuntimeError("Version is already in use")
return await asyncio.shield( return await asyncio.shield(
self.addons.update(addon, version), loop=self.loop) self.addons.update(addon, version), loop=self.loop)
@api_process_raw
def logs(self, request):
"""Return logs from addon."""
addon = self._extract_addon(request)
return self.addons.logs(addon)

View File

@@ -4,8 +4,8 @@ import logging
import voluptuous as vol import voluptuous as vol
from .util import api_process, api_validate from .util import api_process, api_process_raw, api_validate
from ..const import ATTR_VERSION, ATTR_CURRENT from ..const import ATTR_VERSION, ATTR_LAST_VERSION
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -17,18 +17,18 @@ SCHEMA_VERSION = vol.Schema({
class APIHomeAssistant(object): class APIHomeAssistant(object):
"""Handle rest api for homeassistant functions.""" """Handle rest api for homeassistant functions."""
def __init__(self, config, loop, dock_hass): def __init__(self, config, loop, homeassistant):
"""Initialize homeassistant rest api part.""" """Initialize homeassistant rest api part."""
self.config = config self.config = config
self.loop = loop self.loop = loop
self.dock_hass = dock_hass self.homeassistant = homeassistant
@api_process @api_process
async def info(self, request): async def info(self, request):
"""Return host information.""" """Return host information."""
info = { info = {
ATTR_VERSION: self.dock_hass.version, ATTR_VERSION: self.homeassistant.version,
ATTR_CURRENT: self.config.current_homeassistant, ATTR_LAST_VERSION: self.config.last_homeassistant,
} }
return info return info
@@ -37,13 +37,21 @@ class APIHomeAssistant(object):
async def update(self, request): async def update(self, request):
"""Update host OS.""" """Update host OS."""
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.config.current_homeassistant) version = body.get(ATTR_VERSION, self.config.last_homeassistant)
if self.dock_hass.in_progress: if self.homeassistant.in_progress:
raise RuntimeError("Other task is in progress") raise RuntimeError("Other task is in progress")
if version == self.dock_hass.version: if version == self.homeassistant.version:
raise RuntimeError("Version is already in use") raise RuntimeError("Version is already in use")
return await asyncio.shield( return await asyncio.shield(
self.dock_hass.update(version), loop=self.loop) self.homeassistant.update(version), loop=self.loop)
@api_process_raw
def logs(self, request):
"""Return homeassistant docker logs.
Return a coroutine.
"""
return self.homeassistant.logs()

View File

@@ -3,13 +3,13 @@ import logging
import voluptuous as vol import voluptuous as vol
from .util import api_process_hostcontroll, api_process, api_validate from .util import api_process_hostcontrol, api_process, api_validate
from ..const import ATTR_VERSION from ..const import (
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_TYPE, ATTR_HOSTNAME, ATTR_FEATURES,
ATTR_OS)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
UNKNOWN = 'unknown'
SCHEMA_VERSION = vol.Schema({ SCHEMA_VERSION = vol.Schema({
vol.Optional(ATTR_VERSION): vol.Coerce(str), vol.Optional(ATTR_VERSION): vol.Coerce(str),
}) })
@@ -18,44 +18,41 @@ SCHEMA_VERSION = vol.Schema({
class APIHost(object): class APIHost(object):
"""Handle rest api for host functions.""" """Handle rest api for host functions."""
def __init__(self, config, loop, host_controll): def __init__(self, config, loop, host_control):
"""Initialize host rest api part.""" """Initialize host rest api part."""
self.config = config self.config = config
self.loop = loop self.loop = loop
self.host_controll = host_controll self.host_control = host_control
@api_process @api_process
async def info(self, request): async def info(self, request):
"""Return host information.""" """Return host information."""
if not self.host_controll.active: return {
info = { ATTR_TYPE: self.host_control.type,
'os': UNKNOWN, ATTR_VERSION: self.host_control.version,
'version': UNKNOWN, ATTR_LAST_VERSION: self.host_control.last,
'current': UNKNOWN, ATTR_FEATURES: self.host_control.features,
'level': 0, ATTR_HOSTNAME: self.host_control.hostname,
'hostname': UNKNOWN, ATTR_OS: self.host_control.os_info,
} }
return info
return await self.host_controll.info() @api_process_hostcontrol
@api_process_hostcontroll
def reboot(self, request): def reboot(self, request):
"""Reboot host.""" """Reboot host."""
return self.host_controll.reboot() return self.host_control.reboot()
@api_process_hostcontroll @api_process_hostcontrol
def shutdown(self, request): def shutdown(self, request):
"""Poweroff host.""" """Poweroff host."""
return self.host_controll.shutdown() return self.host_control.shutdown()
@api_process_hostcontroll @api_process_hostcontrol
async def update(self, request): async def update(self, request):
"""Update host OS.""" """Update host OS."""
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION) version = body.get(ATTR_VERSION)
if version == self.host_controll.version: if version == self.host_control.version:
raise RuntimeError("Version is already in use") raise RuntimeError("Version is already in use")
return await self.host_controll.host_update(version=version) return await self.host_control.update(version=version)

View File

@@ -1,7 +1,7 @@
"""Init file for HassIO network rest api.""" """Init file for HassIO network rest api."""
import logging import logging
from .util import api_process_hostcontroll from .util import api_process_hostcontrol
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -9,18 +9,18 @@ _LOGGER = logging.getLogger(__name__)
class APINetwork(object): class APINetwork(object):
"""Handle rest api for network functions.""" """Handle rest api for network functions."""
def __init__(self, config, loop, host_controll): def __init__(self, config, loop, host_control):
"""Initialize network rest api part.""" """Initialize network rest api part."""
self.config = config self.config = config
self.loop = loop self.loop = loop
self.host_controll = host_controll self.host_control = host_control
@api_process_hostcontroll @api_process_hostcontrol
def info(self, request): def info(self, request):
"""Show network settings.""" """Show network settings."""
pass pass
@api_process_hostcontroll @api_process_hostcontrol
def options(self, request): def options(self, request):
"""Edit network settings.""" """Edit network settings."""
pass pass

View File

@@ -4,15 +4,16 @@ import logging
import voluptuous as vol import voluptuous as vol
from .util import api_process, api_validate from .util import api_process, api_process_raw, api_validate
from ..const import ( from ..const import (
ATTR_ADDONS, ATTR_VERSION, ATTR_CURRENT, ATTR_BETA, HASSIO_VERSION) ATTR_ADDONS, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_BETA_CHANNEL,
HASSIO_VERSION)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SCHEMA_OPTIONS = vol.Schema({ SCHEMA_OPTIONS = vol.Schema({
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
vol.Optional(ATTR_BETA): vol.Boolean(), vol.Optional(ATTR_BETA_CHANNEL): vol.Boolean(),
}) })
SCHEMA_VERSION = vol.Schema({ SCHEMA_VERSION = vol.Schema({
@@ -23,12 +24,13 @@ SCHEMA_VERSION = vol.Schema({
class APISupervisor(object): class APISupervisor(object):
"""Handle rest api for supervisor functions.""" """Handle rest api for supervisor functions."""
def __init__(self, config, loop, supervisor, addons): def __init__(self, config, loop, supervisor, addons, host_control):
"""Initialize supervisor rest api part.""" """Initialize supervisor rest api part."""
self.config = config self.config = config
self.loop = loop self.loop = loop
self.supervisor = supervisor self.supervisor = supervisor
self.addons = addons self.addons = addons
self.host_control = host_control
@api_process @api_process
async def ping(self, request): async def ping(self, request):
@@ -38,21 +40,20 @@ class APISupervisor(object):
@api_process @api_process
async def info(self, request): async def info(self, request):
"""Return host information.""" """Return host information."""
info = { return {
ATTR_VERSION: HASSIO_VERSION, ATTR_VERSION: HASSIO_VERSION,
ATTR_CURRENT: self.config.current_hassio, ATTR_LAST_VERSION: self.config.last_hassio,
ATTR_BETA: self.config.upstream_beta, ATTR_BETA_CHANNEL: self.config.upstream_beta,
ATTR_ADDONS: self.addons.list, ATTR_ADDONS: self.addons.list_api,
} }
return info
@api_process @api_process
async def options(self, request): async def options(self, request):
"""Set supervisor options.""" """Set supervisor options."""
body = await api_validate(SCHEMA_OPTIONS, request) body = await api_validate(SCHEMA_OPTIONS, request)
if ATTR_BETA in body: if ATTR_BETA_CHANNEL in body:
self.config.upstream_beta = body[ATTR_BETA] self.config.upstream_beta = body[ATTR_BETA_CHANNEL]
return self.config.save() return self.config.save()
@@ -60,7 +61,7 @@ class APISupervisor(object):
async def update(self, request): async def update(self, request):
"""Update supervisor OS.""" """Update supervisor OS."""
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.config.current_hassio) version = body.get(ATTR_VERSION, self.config.last_hassio)
if version == self.supervisor.version: if version == self.supervisor.version:
raise RuntimeError("Version is already in use") raise RuntimeError("Version is already in use")
@@ -71,7 +72,10 @@ class APISupervisor(object):
@api_process @api_process
async def reload(self, request): async def reload(self, request):
"""Reload addons, config ect.""" """Reload addons, config ect."""
tasks = [self.addons.reload(), self.config.fetch_update_infos()] tasks = [
self.addons.reload(), self.config.fetch_update_infos(),
self.host_control.load()
]
results, _ = await asyncio.shield( results, _ = await asyncio.shield(
asyncio.wait(tasks, loop=self.loop), loop=self.loop) asyncio.wait(tasks, loop=self.loop), loop=self.loop)
@@ -80,3 +84,11 @@ class APISupervisor(object):
raise RuntimeError("Some reload task fails!") raise RuntimeError("Some reload task fails!")
return True return True
@api_process_raw
def logs(self, request):
"""Return supervisor docker logs.
Return a coroutine.
"""
return self.supervisor.logs()

View File

@@ -39,11 +39,11 @@ def api_process(method):
return wrap_api return wrap_api
def api_process_hostcontroll(method): def api_process_hostcontrol(method):
"""Wrap HostControll calls to rest api.""" """Wrap HostControl calls to rest api."""
async def wrap_hostcontroll(api, *args, **kwargs): async def wrap_hostcontrol(api, *args, **kwargs):
"""Return host information.""" """Return host information."""
if not api.host_controll.active: if not api.host_control.active:
raise HTTPServiceUnavailable() raise HTTPServiceUnavailable()
try: try:
@@ -59,7 +59,21 @@ def api_process_hostcontroll(method):
return api_return_ok() return api_return_ok()
return api_return_error() return api_return_error()
return wrap_hostcontroll return wrap_hostcontrol
def api_process_raw(method):
"""Wrap function with raw output to rest api."""
async def wrap_api(api, *args, **kwargs):
"""Return api information."""
try:
message = await method(api, *args, **kwargs)
except RuntimeError as err:
message = str(err).encode()
return web.Response(body=message)
return wrap_api
def api_return_error(message=None): def api_return_error(message=None):

View File

@@ -10,10 +10,10 @@ _LOGGER = logging.getLogger(__name__)
HOMEASSISTANT_CONFIG = "{}/homeassistant" HOMEASSISTANT_CONFIG = "{}/homeassistant"
HOMEASSISTANT_IMAGE = 'homeassistant_image' HOMEASSISTANT_IMAGE = 'homeassistant_image'
HOMEASSISTANT_CURRENT = 'homeassistant_current' HOMEASSISTANT_LAST = 'homeassistant_last'
HASSIO_SSL = "{}/ssl" HASSIO_SSL = "{}/ssl"
HASSIO_CURRENT = 'hassio_current' HASSIO_LAST = 'hassio_last'
HASSIO_CLEANUP = 'hassio_cleanup' HASSIO_CLEANUP = 'hassio_cleanup'
ADDONS_REPO = "{}/addons" ADDONS_REPO = "{}/addons"
@@ -67,13 +67,13 @@ class CoreConfig(Config):
async def fetch_update_infos(self): async def fetch_update_infos(self):
"""Read current versions from web.""" """Read current versions from web."""
current = await fetch_current_versions( last = await fetch_current_versions(
self.websession, beta=self.upstream_beta) self.websession, beta=self.upstream_beta)
if current: if last:
self._data.update({ self._data.update({
HOMEASSISTANT_CURRENT: current.get('homeassistant_tag'), HOMEASSISTANT_LAST: last.get('homeassistant'),
HASSIO_CURRENT: current.get('hassio_tag'), HASSIO_LAST: last.get('hassio'),
}) })
self.save() self.save()
return True return True
@@ -120,14 +120,14 @@ class CoreConfig(Config):
return self._data.get(HOMEASSISTANT_IMAGE) return self._data.get(HOMEASSISTANT_IMAGE)
@property @property
def current_homeassistant(self): def last_homeassistant(self):
"""Actual version of homeassistant.""" """Actual version of homeassistant."""
return self._data.get(HOMEASSISTANT_CURRENT) return self._data.get(HOMEASSISTANT_LAST)
@property @property
def current_hassio(self): def last_hassio(self):
"""Actual version of hassio.""" """Actual version of hassio."""
return self._data.get(HASSIO_CURRENT) return self._data.get(HASSIO_LAST)
@property @property
def path_hassio_docker(self): def path_hassio_docker(self):

View File

@@ -1,12 +1,12 @@
"""Const file for HassIO.""" """Const file for HassIO."""
HASSIO_VERSION = '0.11' HASSIO_VERSION = '0.16'
URL_HASSIO_VERSION = \ URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
'https://raw.githubusercontent.com/pvizeli/hassio/master/version.json' 'hassio/master/version.json')
URL_HASSIO_VERSION_BETA = \ URL_HASSIO_VERSION_BETA = ('https://raw.githubusercontent.com/home-assistant/'
'https://raw.githubusercontent.com/pvizeli/hassio/master/version_beta.json' 'hassio/master/version_beta.json')
URL_HASSIO_ADDONS = 'https://github.com/pvizeli/hassio-addons' URL_HASSIO_ADDONS = 'https://github.com/home-assistant/hassio-addons'
DOCKER_REPO = "pvizeli" DOCKER_REPO = "pvizeli"
@@ -31,10 +31,14 @@ JSON_MESSAGE = 'message'
RESULT_ERROR = 'error' RESULT_ERROR = 'error'
RESULT_OK = 'ok' RESULT_OK = 'ok'
ATTR_HOSTNAME = 'hostname'
ATTR_OS = 'os'
ATTR_TYPE = 'type'
ATTR_FEATURES = 'features'
ATTR_ADDONS = 'addons' ATTR_ADDONS = 'addons'
ATTR_VERSION = 'version' ATTR_VERSION = 'version'
ATTR_CURRENT = 'current' ATTR_LAST_VERSION = 'last_version'
ATTR_BETA = 'beta' ATTR_BETA_CHANNEL = 'beta_channel'
ATTR_NAME = 'name' ATTR_NAME = 'name'
ATTR_SLUG = 'slug' ATTR_SLUG = 'slug'
ATTR_DESCRIPTON = 'description' ATTR_DESCRIPTON = 'description'
@@ -45,6 +49,7 @@ ATTR_MAP_CONFIG = 'map_config'
ATTR_MAP_SSL = 'map_ssl' ATTR_MAP_SSL = 'map_ssl'
ATTR_OPTIONS = 'options' ATTR_OPTIONS = 'options'
ATTR_INSTALLED = 'installed' ATTR_INSTALLED = 'installed'
ATTR_DEDICATED = 'dedicated'
ATTR_STATE = 'state' ATTR_STATE = 'state'
ATTR_SCHEMA = 'schema' ATTR_SCHEMA = 'schema'
ATTR_IMAGE = 'image' ATTR_IMAGE = 'image'

View File

@@ -8,7 +8,7 @@ import docker
from . import bootstrap from . import bootstrap
from .addons import AddonManager from .addons import AddonManager
from .api import RestAPI from .api import RestAPI
from .host_controll import HostControll from .host_control import HostControl
from .const import ( from .const import (
SOCKET_DOCKER, RUN_UPDATE_INFO_TASKS, RUN_RELOAD_ADDONS_TASKS, SOCKET_DOCKER, RUN_UPDATE_INFO_TASKS, RUN_RELOAD_ADDONS_TASKS,
RUN_UPDATE_SUPERVISOR_TASKS, STARTUP_AFTER, STARTUP_BEFORE) RUN_UPDATE_SUPERVISOR_TASKS, STARTUP_AFTER, STARTUP_BEFORE)
@@ -40,8 +40,8 @@ class HassIO(object):
self.homeassistant = DockerHomeAssistant( self.homeassistant = DockerHomeAssistant(
self.config, self.loop, self.dock) self.config, self.loop, self.dock)
# init HostControll # init HostControl
self.host_controll = HostControll(self.loop) self.host_control = HostControl(self.loop)
# init addon system # init addon system
self.addons = AddonManager(self.config, self.loop, self.dock) self.addons = AddonManager(self.config, self.loop, self.dock)
@@ -55,20 +55,19 @@ class HassIO(object):
# set api endpoint # set api endpoint
self.config.api_endpoint = await get_local_ip(self.loop) self.config.api_endpoint = await get_local_ip(self.loop)
# hostcontroll # hostcontrol
host_info = await self.host_controll.info() await self.host_control.load()
if host_info: _LOGGER.info(
self.host_controll.version = host_info.get('version') "Connected to HostControl. Type: %s Version: %s Hostname: %s "
_LOGGER.info( "Features: %s", self.host_control.type,
"Connected to HostControll. OS: %s Version: %s Hostname: %s " self.host_control.version, self.host_control.hostname,
"Feature-lvl: %d", host_info.get('os'), self.host_control.features)
host_info.get('version'), host_info.get('hostname'),
host_info.get('level', 0))
# rest api views # rest api views
self.api.register_host(self.host_controll) self.api.register_host(self.host_control)
self.api.register_network(self.host_controll) self.api.register_network(self.host_control)
self.api.register_supervisor(self.supervisor, self.addons) self.api.register_supervisor(
self.supervisor, self.addons, self.host_control)
self.api.register_homeassistant(self.homeassistant) self.api.register_homeassistant(self.homeassistant)
self.api.register_addons(self.addons) self.api.register_addons(self.addons)
@@ -130,10 +129,10 @@ class HassIO(object):
"""Install a homeassistant docker container.""" """Install a homeassistant docker container."""
while True: while True:
# read homeassistant tag and install it # read homeassistant tag and install it
if not self.config.current_homeassistant: if not self.config.last_homeassistant:
await self.config.fetch_update_infos() await self.config.fetch_update_infos()
tag = self.config.current_homeassistant tag = self.config.last_homeassistant
if tag and await self.homeassistant.install(tag): if tag and await self.homeassistant.install(tag):
break break
_LOGGER.warning("Error on setup HomeAssistant. Retry in 60.") _LOGGER.warning("Error on setup HomeAssistant. Retry in 60.")
@@ -144,9 +143,9 @@ class HassIO(object):
async def _hassio_update(self): async def _hassio_update(self):
"""Check and run update of supervisor hassio.""" """Check and run update of supervisor hassio."""
if self.config.current_hassio == self.supervisor.version: if self.config.last_hassio == self.supervisor.version:
return return
_LOGGER.info( _LOGGER.info(
"Found new HassIO version %s.", self.config.current_hassio) "Found new HassIO version %s.", self.config.last_hassio)
await self.supervisor.update(self.config.current_hassio) await self.supervisor.update(self.config.last_hassio)

View File

@@ -99,8 +99,9 @@ class DockerBase(object):
self.container.attrs['Config']['Env']) self.container.attrs['Config']['Env'])
except docker.errors.DockerException: except docker.errors.DockerException:
return False return False
else:
self.container.reload()
self.container.reload()
return self.container.status == 'running' return self.container.status == 'running'
async def attach(self): async def attach(self):
@@ -222,7 +223,6 @@ class DockerBase(object):
Need run inside executor. Need run inside executor.
""" """
old_run = self._is_running()
old_image = "{}:{}".format(self.image, self.version) old_image = "{}:{}".format(self.image, self.version)
_LOGGER.info("Update docker %s with %s:%s", _LOGGER.info("Update docker %s with %s:%s",
@@ -237,9 +237,28 @@ class DockerBase(object):
except docker.errors.DockerException as err: except docker.errors.DockerException as err:
_LOGGER.warning( _LOGGER.warning(
"Can't remove old image %s -> %s", old_image, err) "Can't remove old image %s -> %s", old_image, err)
# restore
if old_run:
self._run()
return True return True
return False return False
async def logs(self):
"""Return docker logs of container."""
if self._lock.locked():
_LOGGER.error("Can't excute logs while a task is in progress")
return False
async with self._lock:
return await self.loop.run_in_executor(None, self._logs)
def _logs(self):
"""Return docker logs of container.
Need run inside executor.
"""
if not self.container:
return
try:
return self.container.logs(tail=100, stdout=True, stderr=True)
except docker.errors.DockerException as err:
_LOGGER.warning("Can't grap logs from %s -> %s", self.image, err)

View File

@@ -24,7 +24,7 @@ class DockerAddon(DockerBase):
@property @property
def docker_name(self): def docker_name(self):
"""Return name of docker container.""" """Return name of docker container."""
return "addon_{}".format(self.addons_data.get_slug(self.addon)) return "addon_{}".format(self.addon)
def _run(self): def _run(self):
"""Run docker image. """Run docker image.
@@ -74,3 +74,17 @@ class DockerAddon(DockerBase):
return False return False
return True return True
def _attach(self):
"""Attach to running docker container.
Need run inside executor.
"""
try:
self.container = self.dock.containers.get(self.docker_name)
self.version = get_version_from_env(
self.container.attrs['Config']['Env'])
_LOGGER.info("Attach to image %s with version %s",
self.image, self.version)
except (docker.errors.DockerException, KeyError):
pass

View File

@@ -62,3 +62,16 @@ class DockerHomeAssistant(DockerBase):
return False return False
return True return True
async def update(self, tag):
"""Update homeassistant docker image."""
if self._lock.locked():
_LOGGER.error("Can't excute update while a task is in progress")
return False
async with self._lock:
if await self.loop.run_in_executor(None, self._update, tag):
await self.loop.run_in_executor(None, self._run)
return True
return False

View File

@@ -1,4 +1,4 @@
"""Host controll for HassIO.""" """Host control for HassIO."""
import asyncio import asyncio
import json import json
import logging import logging
@@ -7,25 +7,35 @@ import stat
import async_timeout import async_timeout
from .const import SOCKET_HC from .const import (
SOCKET_HC, ATTR_LAST_VERSION, ATTR_VERSION, ATTR_TYPE, ATTR_FEATURES,
ATTR_HOSTNAME, ATTR_OS)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
TIMEOUT = 15 TIMEOUT = 15
UNKNOWN = 'unknown'
LEVEL_POWER = 1 FEATURES_SHUTDOWN = 'shutdown'
LEVEL_UPDATE_HOST = 2 FEATURES_REBOOT = 'reboot'
LEVEL_NETWORK = 4 FEATURES_UPDATE = 'update'
FEATURES_NETWORK_INFO = 'network_info'
FEATURES_NETWORK_CONTROL = 'network_control'
class HostControll(object): class HostControl(object):
"""Client for host controll.""" """Client for host control."""
def __init__(self, loop): def __init__(self, loop):
"""Initialize HostControll socket client.""" """Initialize HostControl socket client."""
self.loop = loop self.loop = loop
self.active = False self.active = False
self.version = None self.version = UNKNOWN
self.last = UNKNOWN
self.type = UNKNOWN
self.features = []
self.hostname = UNKNOWN
self.os_info = UNKNOWN
mode = os.stat(SOCKET_HC)[stat.ST_MODE] mode = os.stat(SOCKET_HC)[stat.ST_MODE]
if stat.S_ISSOCK(mode): if stat.S_ISSOCK(mode):
@@ -44,14 +54,14 @@ class HostControll(object):
try: try:
# send # send
_LOGGER.info("Send '%s' to HostControll.", command) _LOGGER.info("Send '%s' to HostControl.", command)
with async_timeout.timeout(TIMEOUT, loop=self.loop): with async_timeout.timeout(TIMEOUT, loop=self.loop):
writer.write("{}\n".format(command).encode()) writer.write("{}\n".format(command).encode())
data = await reader.readline() data = await reader.readline()
response = data.decode() response = data.decode()
_LOGGER.debug("Receive from HostControll: %s.", response) _LOGGER.debug("Receive from HostControl: %s.", response)
if response == "OK": if response == "OK":
return True return True
@@ -63,20 +73,29 @@ class HostControll(object):
try: try:
return json.loads(response) return json.loads(response)
except json.JSONDecodeError: except json.JSONDecodeError:
_LOGGER.warning("Json parse error from HostControll.") _LOGGER.warning("Json parse error from HostControl.")
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.error("Timeout from HostControll!") _LOGGER.error("Timeout from HostControl!")
finally: finally:
writer.close() writer.close()
def info(self): async def load(self):
"""Return Info from host. """Load Info from host.
Return a coroutine. Return a coroutine.
""" """
return self._send_command("info") info = await self._send_command("info")
if not info:
return
self.version = info.get(ATTR_VERSION, UNKNOWN)
self.last = info.get(ATTR_LAST_VERSION, UNKNOWN)
self.type = info.get(ATTR_TYPE, UNKNOWN)
self.features = info.get(ATTR_FEATURES, [])
self.hostname = info.get(ATTR_HOSTNAME, UNKNOWN)
self.os_info = info.get(ATTR_OS, UNKNOWN)
def reboot(self): def reboot(self):
"""Reboot the host system. """Reboot the host system.
@@ -92,11 +111,11 @@ class HostControll(object):
""" """
return self._send_command("shutdown") return self._send_command("shutdown")
def host_update(self, version=None): def update(self, version=None):
"""Update the host system. """Update the host system.
Return a coroutine. Return a coroutine.
""" """
if version: if version:
return self._send_command("host-update {}".format(version)) return self._send_command("update {}".format(version))
return self._send_command("host-update") return self._send_command("update")

View File

@@ -1,6 +1,7 @@
{ {
"hassio_tag": "0.11", "hassio": "0.15",
"homeassistant_tag": "0.43", "homeassistant": "0.43.1",
"resinos_version": "0.4", "resinos": "0.4",
"resinhup_version": "0.1" "resinhup": "0.1",
"generic": "0.2"
} }

View File

@@ -1,6 +1,7 @@
{ {
"hassio_tag": "0.11", "hassio": "0.15",
"homeassistant_tag": "0.43", "homeassistant": "0.43.1",
"resinos_version": "0.4", "resinos": "0.4",
"resinhup_version": "0.1" "resinhup": "0.1",
"generic": "0.2"
} }