Update build system to origin docker (#191)

* Update build system to origin docker

* Rename build env

* fix lint p1

* fix bug & add more log info for snapshot/restore

* fix exception

* Log build info

* revert last change

* fix regex
This commit is contained in:
Pascal Vizeli 2017-09-19 18:06:34 +02:00 committed by GitHub
parent 1353d52bd1
commit 3c04c71401
6 changed files with 103 additions and 83 deletions

View File

@ -566,11 +566,13 @@ class Addon(object):
snapshot.add(self.path_data, arcname="data") snapshot.add(self.path_data, arcname="data")
try: try:
_LOGGER.info("Build snapshot for addon %s", self._id)
await self.loop.run_in_executor(None, _create_tar) await self.loop.run_in_executor(None, _create_tar)
except tarfile.TarError as err: except tarfile.TarError as err:
_LOGGER.error("Can't write tarfile %s -> %s", tar_file, err) _LOGGER.error("Can't write tarfile %s -> %s", tar_file, err)
return False return False
_LOGGER.info("Finish snapshot for addon %s", self._id)
return True return True
async def restore(self, tar_file): async def restore(self, tar_file):
@ -625,6 +627,7 @@ class Addon(object):
shutil.copytree(str(Path(temp, "data")), str(self.path_data)) shutil.copytree(str(Path(temp, "data")), str(self.path_data))
try: try:
_LOGGER.info("Restore data for addon %s", self._id)
await self.loop.run_in_executor(None, _restore_data) await self.loop.run_in_executor(None, _restore_data)
except shutil.Error as err: except shutil.Error as err:
_LOGGER.error("Can't restore origin data -> %s", err) _LOGGER.error("Can't restore origin data -> %s", err)
@ -634,4 +637,5 @@ class Addon(object):
if data[ATTR_STATE] == STATE_STARTED: if data[ATTR_STATE] == STATE_STARTED:
return await self.start() return await self.start()
_LOGGER.info("Finish restore for addon %s", self._id)
return True return True

60
hassio/addons/build.py Normal file
View File

@ -0,0 +1,60 @@
"""HassIO addons build environment."""
from pathlib import Path
from .validate import SCHEMA_BUILD_CONFIG
from ..const import ATTR_SQUASH, ATTR_BUILD_FROM, ATTR_ARGS, META_ADDON
from ..tools import JsonConfig
class AddonBuild(JsonConfig):
"""Handle build options for addons."""
def __init__(self, config, addon):
"""Initialize addon builder."""
self.config = config
self.addon = addon
super().__init__(
Path(addon.path_location, 'build.json'), SCHEMA_BUILD_CONFIG)
def save(self):
"""Ignore save function."""
pass
@property
def base_image(self):
"""Base images for this addon."""
return self._data[ATTR_BUILD_FROM][self.config.arch]
@property
def squash(self):
"""Return True or False if squash is active."""
return self._data[ATTR_SQUASH]
@property
def additional_args(self):
"""Return additional docker build arguments."""
return self._data[ATTR_ARGS]
def get_docker_args(self, version):
"""Create a dict with docker build arguments."""
build_tag = "{}:{}".format(self.addon.image, version)
return {
'path': str(self.addon.path_location),
'tag': build_tag,
'pull': True,
'forcerm': True,
'squash': self.squash,
'labels': {
'io.hass.version': version,
'io.hass.arch': self.config.arch,
'io.hass.type': META_ADDON,
},
'buildargs': {
'BUILD_FROM': self.base_image,
'BUILD_VERSION': version,
'BUILD_ARCH': self.config.arch,
**self.additional_args,
}
}

View File

@ -13,7 +13,8 @@ from ..const import (
ATTR_USER, ATTR_STATE, ATTR_SYSTEM, STATE_STARTED, STATE_STOPPED, ATTR_USER, ATTR_STATE, ATTR_SYSTEM, STATE_STARTED, STATE_STOPPED,
ATTR_LOCATON, ATTR_REPOSITORY, ATTR_TIMEOUT, ATTR_NETWORK, ATTR_LOCATON, ATTR_REPOSITORY, ATTR_TIMEOUT, ATTR_NETWORK,
ATTR_AUTO_UPDATE, ATTR_WEBUI, ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_AUTO_UPDATE, ATTR_WEBUI, ATTR_AUDIO, ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API) ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API, ATTR_BUILD_FROM, ATTR_SQUASH,
ATTR_ARGS)
from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_CHANNEL from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_CHANNEL
@ -54,6 +55,13 @@ PRIVILEGED_ALL = [
"SYS_RAWIO" "SYS_RAWIO"
] ]
BASE_IMAGE = {
ARCH_ARMHF: "homeassistant/armhf-base:latest",
ARCH_AARCH64: "homeassistant/aarch64-base:latest",
ARCH_I386: "homeassistant/i386-base:latest",
ARCH_AMD64: "homeassistant/amd64-base:latest",
}
def _simple_startup(value): def _simple_startup(value):
"""Simple startup schema.""" """Simple startup schema."""
@ -94,7 +102,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
vol.Any(SCHEMA_ELEMENT, {vol.Coerce(str): SCHEMA_ELEMENT}) vol.Any(SCHEMA_ELEMENT, {vol.Coerce(str): SCHEMA_ELEMENT})
], vol.Schema({vol.Coerce(str): SCHEMA_ELEMENT})) ], vol.Schema({vol.Coerce(str): SCHEMA_ELEMENT}))
}), False), }), False),
vol.Optional(ATTR_IMAGE): vol.Match(r"\w*/\w*"), vol.Optional(ATTR_IMAGE): vol.Match(r"^[\-\w]*/[\-\w]*$"),
vol.Optional(ATTR_TIMEOUT, default=10): vol.Optional(ATTR_TIMEOUT, default=10):
vol.All(vol.Coerce(int), vol.Range(min=10, max=120)) vol.All(vol.Coerce(int), vol.Range(min=10, max=120))
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@ -108,6 +116,18 @@ SCHEMA_REPOSITORY_CONFIG = vol.Schema({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
# pylint: disable=no-value-for-parameter
SCHEMA_BUILD_CONFIG = vol.Schema({
vol.Optional(ATTR_BUILD_FROM, default=BASE_IMAGE): vol.Schema({
vol.In(ARCH_ALL): vol.Match(r"^[\-\w]*/[\-\w]*:[\-\w]*$"),
}),
vol.Optional(ATTR_SQUASH, default=False): vol.Boolean(),
vol.Optional(ATTR_ARGS, default={}): vol.Schema({
vol.Coerce(str): vol.Coerce(str)
}),
})
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
SCHEMA_ADDON_USER = vol.Schema({ SCHEMA_ADDON_USER = vol.Schema({
vol.Required(ATTR_VERSION): vol.Coerce(str), vol.Required(ATTR_VERSION): vol.Coerce(str),

View File

@ -55,6 +55,7 @@ ATTR_DATE = 'date'
ATTR_ARCH = 'arch' ATTR_ARCH = 'arch'
ATTR_HOSTNAME = 'hostname' ATTR_HOSTNAME = 'hostname'
ATTR_TIMEZONE = 'timezone' ATTR_TIMEZONE = 'timezone'
ATTR_ARGS = 'args'
ATTR_OS = 'os' ATTR_OS = 'os'
ATTR_TYPE = 'type' ATTR_TYPE = 'type'
ATTR_SOURCE = 'source' ATTR_SOURCE = 'source'
@ -117,6 +118,8 @@ ATTR_OUTPUT = 'output'
ATTR_DISK = 'disk' ATTR_DISK = 'disk'
ATTR_SERIAL = 'serial' ATTR_SERIAL = 'serial'
ATTR_SECURITY = 'security' ATTR_SECURITY = 'security'
ATTR_BUILD_FROM = 'build_from'
ATTR_SQUASH = 'squash'
ATTR_ADDONS_CUSTOM_LIST = 'addons_custom_list' ATTR_ADDONS_CUSTOM_LIST = 'addons_custom_list'
STARTUP_INITIALIZE = 'initialize' STARTUP_INITIALIZE = 'initialize'

View File

@ -1,15 +1,14 @@
"""Init file for HassIO addon docker object.""" """Init file for HassIO addon docker object."""
import logging import logging
from pathlib import Path
import shutil
import docker import docker
import requests import requests
from .interface import DockerInterface from .interface import DockerInterface
from .util import dockerfile_template, docker_process from .util import docker_process
from ..addons.build import AddonBuild
from ..const import ( from ..const import (
META_ADDON, MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE) MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -191,47 +190,21 @@ class DockerAddon(DockerInterface):
Need run inside executor. Need run inside executor.
""" """
build_dir = Path(self.config.path_tmp, self.addon.slug) build_env = AddonBuild(self.config, self.addon)
_LOGGER.info("Start build %s:%s", self.image, tag)
try: try:
# prepare temporary addon build folder image = self.docker.images.build(**build_env.get_docker_args(tag))
try:
source = self.addon.path_location
shutil.copytree(str(source), str(build_dir))
except shutil.Error as err:
_LOGGER.error("Can't copy %s to temporary build folder -> %s",
source, err)
return False
# prepare Dockerfile image.tag(self.image, tag='latest')
try: self.process_metadata(image.attrs, force=True)
dockerfile_template(
Path(build_dir, 'Dockerfile'), self.config.arch,
tag, META_ADDON)
except OSError as err:
_LOGGER.error("Can't prepare dockerfile -> %s", err)
# run docker build except (docker.errors.DockerException) as err:
try: _LOGGER.error("Can't build %s:%s -> %s", self.image, tag, err)
build_tag = "{}:{}".format(self.image, tag) return False
_LOGGER.info("Start build %s on %s", build_tag, build_dir) _LOGGER.info("Build %s:%s done", self.image, tag)
image = self.docker.images.build( return True
path=str(build_dir), tag=build_tag, pull=True,
forcerm=True
)
image.tag(self.image, tag='latest')
self.process_metadata(image.attrs, force=True)
except (docker.errors.DockerException, TypeError) as err:
_LOGGER.error("Can't build %s -> %s", build_tag, err)
return False
_LOGGER.info("Build %s done", build_tag)
return True
finally:
shutil.rmtree(str(build_dir), ignore_errors=True)
@docker_process @docker_process
def export_image(self, path): def export_image(self, path):

View File

@ -1,48 +1,8 @@
"""HassIO docker utilitys.""" """HassIO docker utilitys."""
import logging import logging
import re
from ..const import ARCH_AARCH64, ARCH_ARMHF, ARCH_I386, ARCH_AMD64
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
HASSIO_BASE_IMAGE = {
ARCH_ARMHF: "homeassistant/armhf-base:latest",
ARCH_AARCH64: "homeassistant/aarch64-base:latest",
ARCH_I386: "homeassistant/i386-base:latest",
ARCH_AMD64: "homeassistant/amd64-base:latest",
}
TMPL_IMAGE = re.compile(r"%%BASE_IMAGE%%")
def dockerfile_template(dockerfile, arch, version, meta_type):
"""Prepare a Hass.IO dockerfile."""
buff = []
hassio_image = HASSIO_BASE_IMAGE[arch]
custom_image = re.compile(r"^#{}:FROM".format(arch))
# read docker
with dockerfile.open('r') as dock_input:
for line in dock_input:
line = TMPL_IMAGE.sub(hassio_image, line)
line = custom_image.sub("FROM", line)
buff.append(line)
# add metadata
buff.append(create_metadata(version, arch, meta_type))
# write docker
with dockerfile.open('w') as dock_output:
dock_output.writelines(buff)
def create_metadata(version, arch, meta_type):
"""Generate docker label layer for hassio."""
return ('LABEL io.hass.version="{}" '
'io.hass.arch="{}" '
'io.hass.type="{}"').format(version, arch, meta_type)
# pylint: disable=protected-access # pylint: disable=protected-access
def docker_process(method): def docker_process(method):