WIP: Add support for build docker on local repository (#42)

* Add support for build docker on local repository

* Add docker support

* finish build

* change api

* add dockerfile generator

* finish it

* fix lint

* fix path

* fix path

* fix copy

* add debug stuff

* fix docker template

* cleanups

* fix addons

* change handling

* fix lint / cleanup code

* fix lint

* tag
This commit is contained in:
Pascal Vizeli 2017-05-12 01:37:03 +02:00 committed by GitHub
parent 956af2bd62
commit f4cb16ad09
7 changed files with 149 additions and 19 deletions

View File

@ -14,7 +14,7 @@ from ..const import (
FILE_HASSIO_ADDONS, ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON,
ATTR_STARTUP, ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, BOOT_AUTO,
DOCKER_REPO, ATTR_SCHEMA, ATTR_IMAGE, MAP_CONFIG, MAP_SSL, MAP_ADDONS,
MAP_BACKUP, ATTR_REPOSITORY, ATTR_URL, ATTR_ARCH)
MAP_BACKUP, ATTR_REPOSITORY, ATTR_URL, ATTR_ARCH, ATTR_LOCATON)
from ..config import Config
from ..tools import read_json_file, write_json_file
@ -109,6 +109,7 @@ class AddonsData(Config):
# store
addon_config[ATTR_REPOSITORY] = repository
addon_config[ATTR_LOCATON] = str(addon.parent)
self._addons_cache[addon_slug] = addon_config
except OSError:
@ -303,13 +304,31 @@ class AddonsData(Config):
def get_image(self, addon):
"""Return image name of addon."""
addon_data = self._system_data.get(
addon, self._addons_cache.get(addon))
addon, self._addons_cache.get(addon)
)
if ATTR_IMAGE not in addon_data:
# core repository
if addon_data[ATTR_REPOSITORY] == REPOSITORY_CORE:
return "{}/{}-addon-{}".format(
DOCKER_REPO, self.arch, addon_data[ATTR_SLUG])
return addon_data[ATTR_IMAGE].format(arch=self.arch)
# Repository with dockerhub images
if ATTR_IMAGE in addon_data:
return addon_data[ATTR_IMAGE].format(arch=self.arch)
# Local build addon
if addon_data[ATTR_REPOSITORY] == REPOSITORY_LOCAL:
return "local/{}-addon-{}".format(self.arch, addon_data[ATTR_SLUG])
_LOGGER.error("No image for %s", addon)
def need_build(self, addon):
"""Return True if this addon need a local build."""
addon_data = self._system_data.get(
addon, self._addons_cache.get(addon)
)
return addon_data[ATTR_REPOSITORY] == REPOSITORY_LOCAL \
and not addon_data.get(ATTR_IMAGE)
def map_config(self, addon):
"""Return True if config map is needed."""
@ -333,12 +352,16 @@ class AddonsData(Config):
def path_extern_data(self, addon):
"""Return addon data path external for docker."""
return str(PurePath(self.config.path_extern_addons_data, addon))
return PurePath(self.config.path_extern_addons_data, addon)
def path_addon_options(self, addon):
"""Return path to addons options."""
return Path(self.path_data(addon), "options.json")
def path_addon_location(self, addon):
"""Return path to this addon."""
return Path(self._addons_cache[addon][ATTR_LOCATON])
def write_addon_options(self, addon):
"""Return True if addon options is written to data."""
schema = self.get_schema(addon)

View File

@ -42,6 +42,11 @@ def initialize_system_data(websession):
config.path_addons_git)
config.path_addons_git.mkdir(parents=True)
if not config.path_addons_build.is_dir():
_LOGGER.info("Create Home-Assistant addon build folder %s",
config.path_addons_build)
config.path_addons_build.mkdir(parents=True)
# homeassistant backup folder
if not config.path_backup.is_dir():
_LOGGER.info("Create Home-Assistant backup folder %s",

View File

@ -27,6 +27,7 @@ ADDONS_CORE = PurePath("addons/core")
ADDONS_LOCAL = PurePath("addons/local")
ADDONS_GIT = PurePath("addons/git")
ADDONS_DATA = PurePath("addons/data")
ADDONS_BUILD = PurePath("addons/build")
ADDONS_CUSTOM_LIST = 'addons_custom_list'
BACKUP_DATA = PurePath("backup")
@ -205,7 +206,7 @@ class CoreConfig(Config):
@property
def path_extern_addons_local(self):
"""Return path for customs addons."""
return str(PurePath(self.path_extern_hassio, ADDONS_LOCAL))
return PurePath(self.path_extern_hassio, ADDONS_LOCAL)
@property
def path_addons_data(self):
@ -215,7 +216,12 @@ class CoreConfig(Config):
@property
def path_extern_addons_data(self):
"""Return root addon data folder extern for docker."""
return str(PurePath(self.path_extern_hassio, ADDONS_DATA))
return PurePath(self.path_extern_hassio, ADDONS_DATA)
@property
def path_addons_build(self):
"""Return root addon build folder."""
return Path(HASSIO_SHARE, ADDONS_BUILD)
@property
def path_backup(self):
@ -225,7 +231,7 @@ class CoreConfig(Config):
@property
def path_extern_backup(self):
"""Return root backup data folder extern for docker."""
return str(PurePath(self.path_extern_hassio, BACKUP_DATA))
return PurePath(self.path_extern_hassio, BACKUP_DATA)
@property
def addons_repositories(self):

View File

@ -67,6 +67,7 @@ ATTR_PASSWORD = 'password'
ATTR_TOTP = 'totp'
ATTR_INITIALIZE = 'initialize'
ATTR_SESSION = 'session'
ATTR_LOCATON = 'location'
STARTUP_BEFORE = 'before'
STARTUP_AFTER = 'after'

View File

@ -1,15 +1,16 @@
"""Init file for HassIO addon docker object."""
import logging
from pathlib import Path
import shutil
import docker
from . import DockerBase
from .util import dockerfile_template
from ..tools import get_version_from_env
_LOGGER = logging.getLogger(__name__)
HASS_DOCKER_NAME = 'homeassistant'
class DockerAddon(DockerBase):
"""Docker hassio wrapper for HomeAssistant."""
@ -30,31 +31,31 @@ class DockerAddon(DockerBase):
def volumes(self):
"""Generate volumes for mappings."""
volumes = {
self.addons_data.path_extern_data(self.addon): {
str(self.addons_data.path_extern_data(self.addon)): {
'bind': '/data', 'mode': 'rw'
}}
if self.addons_data.map_config(self.addon):
volumes.update({
self.config.path_extern_config: {
str(self.config.path_extern_config): {
'bind': '/config', 'mode': 'rw'
}})
if self.addons_data.map_ssl(self.addon):
volumes.update({
self.config.path_extern_ssl: {
str(self.config.path_extern_ssl): {
'bind': '/ssl', 'mode': 'rw'
}})
if self.addons_data.map_addons(self.addon):
volumes.update({
self.config.path_extern_addons_local: {
str(self.config.path_extern_addons_local): {
'bind': '/addons', 'mode': 'rw'
}})
if self.addons_data.map_backup(self.addon):
volumes.update({
self.config.path_extern_backup: {
str(self.config.path_extern_backup): {
'bind': '/backup', 'mode': 'rw'
}})
@ -102,7 +103,69 @@ class DockerAddon(DockerBase):
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)
_LOGGER.info(
"Attach to image %s with version %s", self.image, self.version)
except (docker.errors.DockerException, KeyError):
pass
def _install(self, tag):
"""Pull docker image or build it.
Need run inside executor.
"""
if self.addons_data.need_build(self.addon):
return self._build(tag)
return super()._install(tag)
async def build(self, tag):
"""Build a docker container."""
if self._lock.locked():
_LOGGER.error("Can't excute build while a task is in progress")
return False
async with self._lock:
return await self.loop.run_in_executor(None, self._build, tag)
def _build(self, tag):
"""Build a docker container.
Need run inside executor.
"""
build_dir = Path(self.config.path_addons_build, self.addon)
try:
# prepare temporary addon build folder
try:
source = self.addons_data.path_addon_location(self.addon)
shutil.copytree(str(source), str(build_dir))
except shutil.Error as err:
_LOGGER.error("Can't copy %s to temporary build folder -> %s",
source, build_dir)
return False
# prepare Dockerfile
try:
dockerfile_template(
Path(build_dir, 'Dockerfile'), self.addons_data.arch, tag)
except OSError as err:
_LOGGER.error("Can't prepare dockerfile -> %s", err)
# run docker build
try:
build_tag = "{}:{}".format(self.image, tag)
_LOGGER.info("Start build %s on %s", build_tag, build_dir)
image = self.dock.images.build(
path=str(build_dir), tag=build_tag, pull=True)
_LOGGER.info("Build %s done", build_tag)
image.tag(self.image, tag='latest')
except (docker.errors.DockerException, TypeError) as err:
_LOGGER.error("Can't build %s -> %s", build_tag, err)
return False
return True
finally:
shutil.rmtree(str(build_dir), ignore_errors=True)

View File

@ -45,9 +45,9 @@ class DockerHomeAssistant(DockerBase):
'HASSIO': self.config.api_endpoint,
},
volumes={
self.config.path_extern_config:
str(self.config.path_extern_config):
{'bind': '/config', 'mode': 'rw'},
self.config.path_extern_ssl:
str(self.config.path_extern_ssl):
{'bind': '/ssl', 'mode': 'rw'},
})

32
hassio/dock/util.py Normal file
View File

@ -0,0 +1,32 @@
"""HassIO docker utilitys."""
import re
from ..const import ARCH_AARCH64, ARCH_ARMHF, ARCH_I386, ARCH_AMD64
RESIN_BASE_IMAGE = {
ARCH_ARMHF: "resin/armhf-alpine:3.5",
ARCH_AARCH64: "resin/aarch64-alpine:3.5",
ARCH_I386: "resin/i386-alpine:3.5",
ARCH_AMD64: "resin/amd64-alpine:3.5",
}
TMPL_VERSION = re.compile(r"%%VERSION%%")
TMPL_IMAGE = re.compile(r"%%BASE_IMAGE%%")
def dockerfile_template(dockerfile, arch, version):
"""Prepare a Hass.IO dockerfile."""
buff = []
resin_image = RESIN_BASE_IMAGE[arch]
# read docker
with dockerfile.open('r') as dock_input:
for line in dock_input:
line = TMPL_VERSION.sub(version, line)
line = TMPL_IMAGE.sub(resin_image, line)
buff.append(line)
# write docker
with dockerfile.open('w') as dock_output:
dock_output.writelines(buff)