mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-15 13:16:29 +00:00
Code cleanup & add config check to API (#155)
* Code cleanup & add config check to API * Fix comments * fix lint p1 * Fix lint p2 * fix coro * fix parameter * add log output * fix command layout * fix Pannel * fix lint p4 * fix regex * convert to ascii * fix lint p5 * add temp logging * fix output on non-zero exit * remove temporary log
This commit is contained in:
parent
4af92b9d25
commit
7798e7cde2
1
API.md
1
API.md
@ -303,6 +303,7 @@ Output is the raw Docker log.
|
|||||||
|
|
||||||
- POST `/homeassistant/restart`
|
- POST `/homeassistant/restart`
|
||||||
- POST `/homeassistant/options`
|
- POST `/homeassistant/options`
|
||||||
|
- POST `/homeassistant/check`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
@ -68,10 +68,11 @@ class RestAPI(object):
|
|||||||
api_hass = APIHomeAssistant(self.config, self.loop, dock_homeassistant)
|
api_hass = APIHomeAssistant(self.config, self.loop, dock_homeassistant)
|
||||||
|
|
||||||
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/logs', api_hass.logs)
|
||||||
self.webapp.router.add_post('/homeassistant/options', api_hass.options)
|
self.webapp.router.add_post('/homeassistant/options', api_hass.options)
|
||||||
self.webapp.router.add_post('/homeassistant/update', api_hass.update)
|
self.webapp.router.add_post('/homeassistant/update', api_hass.update)
|
||||||
self.webapp.router.add_post('/homeassistant/restart', api_hass.restart)
|
self.webapp.router.add_post('/homeassistant/restart', api_hass.restart)
|
||||||
self.webapp.router.add_get('/homeassistant/logs', api_hass.logs)
|
self.webapp.router.add_post('/homeassistant/check', api_hass.check)
|
||||||
|
|
||||||
def register_addons(self, addons):
|
def register_addons(self, addons):
|
||||||
"""Register homeassistant function."""
|
"""Register homeassistant function."""
|
||||||
|
@ -170,19 +170,13 @@ class APIAddons(object):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def uninstall(self, request):
|
def uninstall(self, request):
|
||||||
"""Uninstall addon.
|
"""Uninstall addon."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return asyncio.shield(addon.uninstall(), loop=self.loop)
|
return asyncio.shield(addon.uninstall(), loop=self.loop)
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def start(self, request):
|
def start(self, request):
|
||||||
"""Start addon.
|
"""Start addon."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
# check options
|
# check options
|
||||||
@ -196,10 +190,7 @@ class APIAddons(object):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def stop(self, request):
|
def stop(self, request):
|
||||||
"""Stop addon.
|
"""Stop addon."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return asyncio.shield(addon.stop(), loop=self.loop)
|
return asyncio.shield(addon.stop(), loop=self.loop)
|
||||||
|
|
||||||
@ -218,19 +209,13 @@ class APIAddons(object):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request):
|
def restart(self, request):
|
||||||
"""Restart addon.
|
"""Restart addon."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return asyncio.shield(addon.restart(), loop=self.loop)
|
return asyncio.shield(addon.restart(), loop=self.loop)
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request):
|
def logs(self, request):
|
||||||
"""Return logs from addon.
|
"""Return logs from addon."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return addon.logs()
|
return addon.logs()
|
||||||
|
|
||||||
|
@ -73,16 +73,19 @@ class APIHomeAssistant(object):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request):
|
def restart(self, request):
|
||||||
"""Restart homeassistant.
|
"""Restart homeassistant."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
return asyncio.shield(self.homeassistant.restart(), loop=self.loop)
|
return asyncio.shield(self.homeassistant.restart(), loop=self.loop)
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request):
|
def logs(self, request):
|
||||||
"""Return homeassistant docker logs.
|
"""Return homeassistant docker logs."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
return self.homeassistant.logs()
|
return self.homeassistant.logs()
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def check(self, request):
|
||||||
|
"""Check config of homeassistant."""
|
||||||
|
code, message = await self.homeassistant.check_config()
|
||||||
|
if not code:
|
||||||
|
raise RuntimeError(message)
|
||||||
|
|
||||||
|
return True
|
||||||
|
@ -59,18 +59,12 @@ class APIHost(object):
|
|||||||
|
|
||||||
@api_process_hostcontrol
|
@api_process_hostcontrol
|
||||||
def reboot(self, request):
|
def reboot(self, request):
|
||||||
"""Reboot host.
|
"""Reboot host."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
return self.host_control.reboot()
|
return self.host_control.reboot()
|
||||||
|
|
||||||
@api_process_hostcontrol
|
@api_process_hostcontrol
|
||||||
def shutdown(self, request):
|
def shutdown(self, request):
|
||||||
"""Poweroff host.
|
"""Poweroff host."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
return self.host_control.shutdown()
|
return self.host_control.shutdown()
|
||||||
|
|
||||||
@api_process_hostcontrol
|
@api_process_hostcontrol
|
||||||
|
@ -112,10 +112,7 @@ class APISnapshots(object):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restore_full(self, request):
|
def restore_full(self, request):
|
||||||
"""Full-Restore a snapshot.
|
"""Full-Restore a snapshot."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
snapshot = self._extract_snapshot(request)
|
snapshot = self._extract_snapshot(request)
|
||||||
return asyncio.shield(
|
return asyncio.shield(
|
||||||
self.snapshots.do_restore_full(snapshot), loop=self.loop)
|
self.snapshots.do_restore_full(snapshot), loop=self.loop)
|
||||||
|
@ -121,8 +121,5 @@ class APISupervisor(object):
|
|||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request):
|
def logs(self, request):
|
||||||
"""Return supervisor docker logs.
|
"""Return supervisor docker logs."""
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
return self.supervisor.logs()
|
return self.supervisor.logs()
|
||||||
|
@ -5,6 +5,7 @@ import logging
|
|||||||
|
|
||||||
import docker
|
import docker
|
||||||
|
|
||||||
|
from .util import docker_process
|
||||||
from ..const import LABEL_VERSION, LABEL_ARCH
|
from ..const import LABEL_VERSION, LABEL_ARCH
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -52,14 +53,10 @@ class DockerBase(object):
|
|||||||
if need_arch and LABEL_ARCH in metadata['Config']['Labels']:
|
if need_arch and LABEL_ARCH in metadata['Config']['Labels']:
|
||||||
self.arch = metadata['Config']['Labels'][LABEL_ARCH]
|
self.arch = metadata['Config']['Labels'][LABEL_ARCH]
|
||||||
|
|
||||||
async def install(self, tag):
|
@docker_process
|
||||||
|
def install(self, tag):
|
||||||
"""Pull docker image."""
|
"""Pull docker image."""
|
||||||
if self._lock.locked():
|
return self.loop.run_in_executor(None, self._install, tag)
|
||||||
_LOGGER.error("Can't excute install while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
return await self.loop.run_in_executor(None, self._install, tag)
|
|
||||||
|
|
||||||
def _install(self, tag):
|
def _install(self, tag):
|
||||||
"""Pull docker image.
|
"""Pull docker image.
|
||||||
@ -80,10 +77,7 @@ class DockerBase(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def exists(self):
|
def exists(self):
|
||||||
"""Return True if docker image exists in local repo.
|
"""Return True if docker image exists in local repo."""
|
||||||
|
|
||||||
Return a Future.
|
|
||||||
"""
|
|
||||||
return self.loop.run_in_executor(None, self._exists)
|
return self.loop.run_in_executor(None, self._exists)
|
||||||
|
|
||||||
def _exists(self):
|
def _exists(self):
|
||||||
@ -126,14 +120,10 @@ class DockerBase(object):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def attach(self):
|
@docker_process
|
||||||
|
def attach(self):
|
||||||
"""Attach to running docker container."""
|
"""Attach to running docker container."""
|
||||||
if self._lock.locked():
|
return self.loop.run_in_executor(None, self._attach)
|
||||||
_LOGGER.error("Can't excute attach while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
return await self.loop.run_in_executor(None, self._attach)
|
|
||||||
|
|
||||||
def _attach(self):
|
def _attach(self):
|
||||||
"""Attach to running docker container.
|
"""Attach to running docker container.
|
||||||
@ -154,14 +144,10 @@ class DockerBase(object):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def run(self):
|
@docker_process
|
||||||
|
def run(self):
|
||||||
"""Run docker image."""
|
"""Run docker image."""
|
||||||
if self._lock.locked():
|
return self.loop.run_in_executor(None, self._run)
|
||||||
_LOGGER.error("Can't excute run while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
return await self.loop.run_in_executor(None, self._run)
|
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
"""Run docker image.
|
"""Run docker image.
|
||||||
@ -170,15 +156,10 @@ class DockerBase(object):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
async def stop(self):
|
@docker_process
|
||||||
|
def stop(self):
|
||||||
"""Stop/remove docker container."""
|
"""Stop/remove docker container."""
|
||||||
if self._lock.locked():
|
return self.loop.run_in_executor(None, self._stop)
|
||||||
_LOGGER.error("Can't excute stop while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
await self.loop.run_in_executor(None, self._stop)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _stop(self):
|
def _stop(self):
|
||||||
"""Stop/remove and remove docker container.
|
"""Stop/remove and remove docker container.
|
||||||
@ -188,7 +169,7 @@ class DockerBase(object):
|
|||||||
try:
|
try:
|
||||||
container = self.dock.containers.get(self.name)
|
container = self.dock.containers.get(self.name)
|
||||||
except docker.errors.DockerException:
|
except docker.errors.DockerException:
|
||||||
return
|
return False
|
||||||
|
|
||||||
if container.status == 'running':
|
if container.status == 'running':
|
||||||
_LOGGER.info("Stop %s docker application", self.image)
|
_LOGGER.info("Stop %s docker application", self.image)
|
||||||
@ -199,14 +180,12 @@ class DockerBase(object):
|
|||||||
_LOGGER.info("Clean %s docker application", self.image)
|
_LOGGER.info("Clean %s docker application", self.image)
|
||||||
container.remove(force=True)
|
container.remove(force=True)
|
||||||
|
|
||||||
async def remove(self):
|
return True
|
||||||
"""Remove docker images."""
|
|
||||||
if self._lock.locked():
|
|
||||||
_LOGGER.error("Can't excute remove while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
@docker_process
|
||||||
return await self.loop.run_in_executor(None, self._remove)
|
def remove(self):
|
||||||
|
"""Remove docker images."""
|
||||||
|
return self.loop.run_in_executor(None, self._remove)
|
||||||
|
|
||||||
def _remove(self):
|
def _remove(self):
|
||||||
"""remove docker images.
|
"""remove docker images.
|
||||||
@ -235,16 +214,13 @@ class DockerBase(object):
|
|||||||
# clean metadata
|
# clean metadata
|
||||||
self.version = None
|
self.version = None
|
||||||
self.arch = None
|
self.arch = None
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def update(self, tag):
|
@docker_process
|
||||||
|
def update(self, tag):
|
||||||
"""Update a docker image."""
|
"""Update a docker image."""
|
||||||
if self._lock.locked():
|
return self.loop.run_in_executor(None, self._update, tag)
|
||||||
_LOGGER.error("Can't excute update while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
return await self.loop.run_in_executor(None, self._update, tag)
|
|
||||||
|
|
||||||
def _update(self, tag):
|
def _update(self, tag):
|
||||||
"""Update a docker image.
|
"""Update a docker image.
|
||||||
@ -258,22 +234,16 @@ class DockerBase(object):
|
|||||||
if not self._install(tag):
|
if not self._install(tag):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# container
|
# stop container & cleanup
|
||||||
self._stop()
|
self._stop()
|
||||||
|
|
||||||
# cleanup images
|
|
||||||
self._cleanup()
|
self._cleanup()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def logs(self):
|
@docker_process
|
||||||
|
def logs(self):
|
||||||
"""Return docker logs of container."""
|
"""Return docker logs of container."""
|
||||||
if self._lock.locked():
|
return self.loop.run_in_executor(None, self._logs)
|
||||||
_LOGGER.error("Can't excute logs while a task is in progress")
|
|
||||||
return b""
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
return await self.loop.run_in_executor(None, self._logs)
|
|
||||||
|
|
||||||
def _logs(self):
|
def _logs(self):
|
||||||
"""Return docker logs of container.
|
"""Return docker logs of container.
|
||||||
@ -290,14 +260,10 @@ class DockerBase(object):
|
|||||||
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)
|
||||||
|
|
||||||
async def restart(self):
|
@docker_process
|
||||||
|
def restart(self):
|
||||||
"""Restart docker container."""
|
"""Restart docker container."""
|
||||||
if self._lock.locked():
|
return self.loop.run_in_executor(None, self._restart)
|
||||||
_LOGGER.error("Can't excute restart while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
return await self.loop.run_in_executor(None, self._restart)
|
|
||||||
|
|
||||||
def _restart(self):
|
def _restart(self):
|
||||||
"""Restart docker container.
|
"""Restart docker container.
|
||||||
@ -319,14 +285,10 @@ class DockerBase(object):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def cleanup(self):
|
@docker_process
|
||||||
|
def cleanup(self):
|
||||||
"""Check if old version exists and cleanup."""
|
"""Check if old version exists and cleanup."""
|
||||||
if self._lock.locked():
|
return self.loop.run_in_executor(None, self._cleanup)
|
||||||
_LOGGER.error("Can't excute cleanup while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
await self.loop.run_in_executor(None, self._cleanup)
|
|
||||||
|
|
||||||
def _cleanup(self):
|
def _cleanup(self):
|
||||||
"""Check if old version exists and cleanup.
|
"""Check if old version exists and cleanup.
|
||||||
@ -337,7 +299,7 @@ class DockerBase(object):
|
|||||||
latest = self.dock.images.get(self.image)
|
latest = self.dock.images.get(self.image)
|
||||||
except docker.errors.DockerException:
|
except docker.errors.DockerException:
|
||||||
_LOGGER.warning("Can't find %s for cleanup", self.image)
|
_LOGGER.warning("Can't find %s for cleanup", self.image)
|
||||||
return
|
return False
|
||||||
|
|
||||||
for image in self.dock.images.list(name=self.image):
|
for image in self.dock.images.list(name=self.image):
|
||||||
if latest.id == image.id:
|
if latest.id == image.id:
|
||||||
@ -346,3 +308,17 @@ class DockerBase(object):
|
|||||||
with suppress(docker.errors.DockerException):
|
with suppress(docker.errors.DockerException):
|
||||||
_LOGGER.info("Cleanup docker images: %s", image.tags)
|
_LOGGER.info("Cleanup docker images: %s", image.tags)
|
||||||
self.dock.images.remove(image.id, force=True)
|
self.dock.images.remove(image.id, force=True)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@docker_process
|
||||||
|
def execute_command(self, command):
|
||||||
|
"""Create a temporary container and run command."""
|
||||||
|
return self.loop.run_in_executor(None, self._execute_command, command)
|
||||||
|
|
||||||
|
def _execute_command(self, command):
|
||||||
|
"""Create a temporary container and run command.
|
||||||
|
|
||||||
|
Need run inside executor.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
@ -7,7 +7,7 @@ import docker
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from . import DockerBase
|
from . import DockerBase
|
||||||
from .util import dockerfile_template
|
from .util import dockerfile_template, docker_process
|
||||||
from ..const import (
|
from ..const import (
|
||||||
META_ADDON, MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE)
|
META_ADDON, MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE)
|
||||||
|
|
||||||
@ -218,15 +218,10 @@ class DockerAddon(DockerBase):
|
|||||||
finally:
|
finally:
|
||||||
shutil.rmtree(str(build_dir), ignore_errors=True)
|
shutil.rmtree(str(build_dir), ignore_errors=True)
|
||||||
|
|
||||||
async def export_image(self, path):
|
@docker_process
|
||||||
|
def export_image(self, path):
|
||||||
"""Export current images into a tar file."""
|
"""Export current images into a tar file."""
|
||||||
if self._lock.locked():
|
return self.loop.run_in_executor(None, self._export_image, path)
|
||||||
_LOGGER.error("Can't excute export while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
return await self.loop.run_in_executor(
|
|
||||||
None, self._export_image, path)
|
|
||||||
|
|
||||||
def _export_image(self, tar_file):
|
def _export_image(self, tar_file):
|
||||||
"""Export current images into a tar file.
|
"""Export current images into a tar file.
|
||||||
@ -250,15 +245,10 @@ class DockerAddon(DockerBase):
|
|||||||
_LOGGER.info("Export image %s to %s", self.image, tar_file)
|
_LOGGER.info("Export image %s to %s", self.image, tar_file)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def import_image(self, path, tag):
|
@docker_process
|
||||||
|
def import_image(self, path, tag):
|
||||||
"""Import a tar file as image."""
|
"""Import a tar file as image."""
|
||||||
if self._lock.locked():
|
return self.loop.run_in_executor(None, self._import_image, path, tag)
|
||||||
_LOGGER.error("Can't excute import while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
return await self.loop.run_in_executor(
|
|
||||||
None, self._import_image, path, tag)
|
|
||||||
|
|
||||||
def _import_image(self, tar_file, tag):
|
def _import_image(self, tar_file, tag):
|
||||||
"""Import a tar file as image.
|
"""Import a tar file as image.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Init file for HassIO docker object."""
|
"""Init file for HassIO docker object."""
|
||||||
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
@ -66,7 +67,8 @@ class DockerHomeAssistant(DockerBase):
|
|||||||
{'bind': '/ssl', 'mode': 'ro'},
|
{'bind': '/ssl', 'mode': 'ro'},
|
||||||
str(self.config.path_extern_share):
|
str(self.config.path_extern_share):
|
||||||
{'bind': '/share', 'mode': 'rw'},
|
{'bind': '/share', 'mode': 'rw'},
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't run %s -> %s", self.image, err)
|
_LOGGER.error("Can't run %s -> %s", self.image, err)
|
||||||
@ -75,3 +77,41 @@ class DockerHomeAssistant(DockerBase):
|
|||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Start homeassistant %s with version %s", self.image, self.version)
|
"Start homeassistant %s with version %s", self.image, self.version)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _execute_command(self, command):
|
||||||
|
"""Create a temporary container and run command.
|
||||||
|
|
||||||
|
Need run inside executor.
|
||||||
|
"""
|
||||||
|
_LOGGER.info("Run command '%s' on %s", command, self.image)
|
||||||
|
try:
|
||||||
|
container = self.dock.containers.run(
|
||||||
|
self.image,
|
||||||
|
command=command,
|
||||||
|
detach=True,
|
||||||
|
stdout=True,
|
||||||
|
stderr=True,
|
||||||
|
environment={
|
||||||
|
'TZ': self.config.timezone,
|
||||||
|
},
|
||||||
|
volumes={
|
||||||
|
str(self.config.path_extern_config):
|
||||||
|
{'bind': '/config', 'mode': 'ro'},
|
||||||
|
str(self.config.path_extern_ssl):
|
||||||
|
{'bind': '/ssl', 'mode': 'ro'},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait until command is done
|
||||||
|
container.wait()
|
||||||
|
output = container.logs()
|
||||||
|
|
||||||
|
except docker.errors.DockerException as err:
|
||||||
|
_LOGGER.error("Can't execute command -> %s", err)
|
||||||
|
return b""
|
||||||
|
|
||||||
|
# cleanup container
|
||||||
|
with suppress(docker.errors.DockerException):
|
||||||
|
container.remove(force=True)
|
||||||
|
|
||||||
|
return output
|
||||||
|
@ -3,6 +3,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from . import DockerBase
|
from . import DockerBase
|
||||||
|
from .util import docker_process
|
||||||
from ..const import RESTART_EXIT_CODE
|
from ..const import RESTART_EXIT_CODE
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -21,20 +22,16 @@ class DockerSupervisor(DockerBase):
|
|||||||
"""Return name of docker container."""
|
"""Return name of docker container."""
|
||||||
return os.environ['SUPERVISOR_NAME']
|
return os.environ['SUPERVISOR_NAME']
|
||||||
|
|
||||||
|
@docker_process
|
||||||
async def update(self, tag):
|
async def update(self, tag):
|
||||||
"""Update a supervisor docker image."""
|
"""Update a supervisor docker image."""
|
||||||
if self._lock.locked():
|
|
||||||
_LOGGER.error("Can't excute update while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
_LOGGER.info("Update supervisor docker to %s:%s", self.image, tag)
|
_LOGGER.info("Update supervisor docker to %s:%s", self.image, tag)
|
||||||
|
|
||||||
async with self._lock:
|
if await self.loop.run_in_executor(None, self._install, tag):
|
||||||
if await self.loop.run_in_executor(None, self._install, tag):
|
self.loop.create_task(self.stop_callback(RESTART_EXIT_CODE))
|
||||||
self.loop.create_task(self.stop_callback(RESTART_EXIT_CODE))
|
return True
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""Run docker image."""
|
"""Run docker image."""
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
"""HassIO docker utilitys."""
|
"""HassIO docker utilitys."""
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from ..const import ARCH_AARCH64, ARCH_ARMHF, ARCH_I386, ARCH_AMD64
|
from ..const import ARCH_AARCH64, ARCH_ARMHF, ARCH_I386, ARCH_AMD64
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
HASSIO_BASE_IMAGE = {
|
HASSIO_BASE_IMAGE = {
|
||||||
ARCH_ARMHF: "homeassistant/armhf-base:latest",
|
ARCH_ARMHF: "homeassistant/armhf-base:latest",
|
||||||
@ -40,3 +42,19 @@ def create_metadata(version, arch, meta_type):
|
|||||||
return ('LABEL io.hass.version="{}" '
|
return ('LABEL io.hass.version="{}" '
|
||||||
'io.hass.arch="{}" '
|
'io.hass.arch="{}" '
|
||||||
'io.hass.type="{}"').format(version, arch, meta_type)
|
'io.hass.type="{}"').format(version, arch, meta_type)
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
@ -2,16 +2,19 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION,
|
FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION,
|
||||||
ATTR_VERSION)
|
ATTR_VERSION)
|
||||||
from .dock.homeassistant import DockerHomeAssistant
|
from .dock.homeassistant import DockerHomeAssistant
|
||||||
from .tools import JsonConfig
|
from .tools import JsonConfig, convert_to_ascii
|
||||||
from .validate import SCHEMA_HASS_CONFIG
|
from .validate import SCHEMA_HASS_CONFIG
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
RE_CONFIG_CHECK = re.compile(r"error", re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistant(JsonConfig):
|
class HomeAssistant(JsonConfig):
|
||||||
"""Hass core object for handle it."""
|
"""Hass core object for handle it."""
|
||||||
@ -165,3 +168,20 @@ class HomeAssistant(JsonConfig):
|
|||||||
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.docker.in_progress
|
return self.docker.in_progress
|
||||||
|
|
||||||
|
async def check_config(self):
|
||||||
|
"""Run homeassistant config check."""
|
||||||
|
log = await self.docker.execute_command(
|
||||||
|
"python3 -m homeassistant -c /config --script check_config"
|
||||||
|
)
|
||||||
|
|
||||||
|
# if not valid
|
||||||
|
if not log:
|
||||||
|
return (False, "")
|
||||||
|
|
||||||
|
# parse output
|
||||||
|
log = convert_to_ascii(log)
|
||||||
|
if RE_CONFIG_CHECK.search(log):
|
||||||
|
return (False, log)
|
||||||
|
|
||||||
|
return (True, log)
|
||||||
|
@ -5,6 +5,7 @@ from datetime import datetime
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
import re
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import async_timeout
|
import async_timeout
|
||||||
@ -15,6 +16,8 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
FREEGEOIP_URL = "https://freegeoip.io/json/"
|
FREEGEOIP_URL = "https://freegeoip.io/json/"
|
||||||
|
|
||||||
|
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
|
||||||
|
|
||||||
|
|
||||||
def get_local_ip(loop):
|
def get_local_ip(loop):
|
||||||
"""Retrieve local IP address.
|
"""Retrieve local IP address.
|
||||||
@ -68,6 +71,11 @@ async def fetch_timezone(websession):
|
|||||||
return data.get('time_zone', 'UTC')
|
return data.get('time_zone', 'UTC')
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to_ascii(raw):
|
||||||
|
"""Convert binary to ascii and remove colors."""
|
||||||
|
return RE_STRING.sub("", raw.decode())
|
||||||
|
|
||||||
|
|
||||||
class JsonConfig(object):
|
class JsonConfig(object):
|
||||||
"""Hass core object for handle it."""
|
"""Hass core object for handle it."""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user