Skip to content

Commit

Permalink
build: Use explicit build context for metalk8s-ui
Browse files Browse the repository at this point in the history
This will prevent the Docker client from tarring up the whole `_build`
directory (and sometimes failing when multiple processes are writing to
this directory).

Fixes: #3271
  • Loading branch information
gdemonet committed May 6, 2021
1 parent 127ea68 commit 618f9f3
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 14 deletions.
12 changes: 9 additions & 3 deletions buildchain/buildchain/docker_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,19 @@ def decorated_task(*args: Any, **kwargs: Any) -> Optional[TaskError]:
})
def docker_build(image: 'LocalImage') -> None:
"""Build a Docker image using Docker API."""
DOCKER_CLIENT.images.build(
kwargs = dict(
tag=image.tag,
path=str(image.build_context),
dockerfile=str(image.dockerfile),
buildargs=image.build_args,
forcerm=True,
)
if image.custom_context:
kwargs["fileobj"] = image.build_context.build_tar()
kwargs["custom_context"] = True
else:
kwargs["path"] = str(image.build_context)
kwargs["dockerfile"] = str(image.dockerfile)

DOCKER_CLIENT.images.build(**kwargs)


class DockerRun:
Expand Down
10 changes: 9 additions & 1 deletion buildchain/buildchain/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,15 @@ def _operator_image(name: str, **kwargs: Any) -> targets.OperatorImage:
),
_local_image(
name='metalk8s-ui',
build_context=config.BUILD_ROOT,
build_context=targets.ExplicitContext(
dockerfile=constants.ROOT/"images/metalk8s-ui/Dockerfile",
base_dir=config.BUILD_ROOT,
contents=[
constants.UI_BUILD_ROOT.relative_to(config.BUILD_ROOT),
constants.DOCS_BUILD_ROOT.relative_to(config.BUILD_ROOT),
"metalk8s-ui-nginx.conf",
],
),
build_args={
'NGINX_IMAGE_VERSION': versions.NGINX_IMAGE_VERSION,
},
Expand Down
4 changes: 2 additions & 2 deletions buildchain/buildchain/targets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from buildchain.targets.checksum import Sha256Sum
from buildchain.targets.directory import Mkdir
from buildchain.targets.file_tree import FileTree
from buildchain.targets.local_image import LocalImage
from buildchain.targets.local_image import LocalImage, ExplicitContext
from buildchain.targets.operator_image import OperatorImage
from buildchain.targets.package import Package, RPMPackage, DEBPackage
from buildchain.targets.remote_image import (
Expand All @@ -28,7 +28,7 @@
'Sha256Sum',
'Mkdir',
'FileTree',
'LocalImage',
'LocalImage', 'ExplicitContext',
'OperatorImage',
'Package', 'RPMPackage', 'DEBPackage',
'ImageSaveFormat', 'RemoteImage', 'SaveAsLayers', 'SaveAsTar',
Expand Down
57 changes: 49 additions & 8 deletions buildchain/buildchain/targets/local_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
All of these actions are done by a single task.
"""


from io import BytesIO
import operator
import os
import re
from pathlib import Path
import tarfile
from typing import Any, Dict, List, Optional, Union

from buildchain import config
Expand All @@ -29,16 +30,36 @@
from . import image


class ExplicitContext: # pylint: disable=too-few-public-methods
"""An explicit build context, to pass to a `LocalImage` target."""
def __init__(
self, dockerfile: Path, base_dir: Path, contents: List[Union[str, Path]]
) -> None:
self.dockerfile = dockerfile
self.base_dir = base_dir
self.contents = contents

def build_tar(self) -> BytesIO:
"""Build a tar archive in memory for this `docker build` context."""
fileobj = BytesIO()
with tarfile.open(fileobj=fileobj, mode='w') as tar:
tar.add(str(self.dockerfile), arcname="Dockerfile")
for item in self.contents:
tar.add(str(self.base_dir / item), arcname=str(item))
fileobj.seek(0)
return fileobj


class LocalImage(image.ContainerImage):
"""A locally built container image."""
def __init__(
self,
name: str,
version: str,
dockerfile: Path,
destination: Path,
save_on_disk: bool,
build_context: Optional[Path]=None,
dockerfile: Optional[Path]=None,
build_context: Optional[Union[Path, ExplicitContext]]=None,
build_args: Optional[Dict[str, Any]]=None,
**kwargs: Any
):
Expand All @@ -50,16 +71,30 @@ def __init__(
dockerfile: path to the Dockerfile
destination: where to save the result
save_on_disk: save the image on disk?
build_context: path to the build context
(default to the directory containing the Dockerfile)
build_context: path to the build context, or an ExplicitContext
instance (defaults to the directory containing the
Dockerfile)
build_args: build arguments
Keyword Arguments:
They are passed to `Target` init method.
"""
self._dockerfile = dockerfile
self._build_context : Union[Path, ExplicitContext]
if isinstance(build_context, ExplicitContext):
self._custom_context = True
self._dockerfile = build_context.dockerfile
self._build_context = build_context
else:
self._custom_context = False
if dockerfile is None:
raise ValueError(
"Must provide a `dockerfile` if `build_context` is not "
"an `ExplicitContext`"
)
self._dockerfile = dockerfile
self._build_context = build_context or self.dockerfile.parent

self._save = save_on_disk
self._build_context = build_context or self.dockerfile.parent
self._build_args = build_args or {}
kwargs.setdefault('file_dep', []).append(self.dockerfile)
kwargs.setdefault('task_dep', []).append('check_for:skopeo')
Expand All @@ -73,6 +108,7 @@ def __init__(
dockerfile = property(operator.attrgetter('_dockerfile'))
save_on_disk = property(operator.attrgetter('_save'))
build_context = property(operator.attrgetter('_build_context'))
custom_context = property(operator.attrgetter('_custom_context'))
build_args = property(operator.attrgetter('_build_args'))
dep_re = re.compile(
r'^\s*(COPY|ADD)( --[^ ]+)* (?P<src>[^ ]+) (?P<dst>[^ ]+)\s*$'
Expand All @@ -85,6 +121,11 @@ def load_deps_from_dockerfile(self) -> Dict[str, Any]:
with self.dockerfile.open('r', encoding='utf8') as dockerfile:
docker_lines = dockerfile.readlines()

if self.custom_context:
search_path = self.build_context.base_dir
else:
search_path = self.build_context

dep_matches = [
self.dep_re.match(line.strip())
for line in docker_lines
Expand All @@ -94,7 +135,7 @@ def load_deps_from_dockerfile(self) -> Dict[str, Any]:
for match in dep_matches:
if match is None:
continue
deps.extend(self._expand_dep(self.build_context / match.group('src')))
deps.extend(self._expand_dep(search_path / match.group('src')))

# NOTE: we **must** convert to string here, otherwise the serialization
# fails and the build exits with return code 3.
Expand Down

0 comments on commit 618f9f3

Please sign in to comment.