Repository: cekit/cekit

Issue:
Add support for supplementary dist-git items
We have a use case for committing another yaml file for CVP (gating.yaml) which would currently be ignored by cekit, is it possible to add capabilities within cekit to define a list of supplementary items that would be synced to the dist-git at runtime? Or at the very least, support this additional file like with content_sets.yaml.

Thank you!

Discussion:
luck3y: Not a big fan of the dist name, I think of that being used to build a pkg distribution - how about buildcfg, distcfg or builddesc / distcfg?

It would be nice if it were configurable in image.yaml :)
goldmann: @rnc Do you have any comments?
goldmann: Points for discussion are available here: https://github.com/cekit/cekit/issues/394#issuecomment-495525352
goldmann: Some changes were made to the implementation.

* Default directory name is now `osbs_extra`
* The directory name can be redefined within the image descriptor with the `extra_dir` key in the `osbs` section:
    ```yaml
    osbs:
      extra_dir: custom_name
    ```

Any comments?

Patch:
diff --git a/cekit/builders/osbs.py b/cekit/builders/osbs.py
index 808c404..8d01362 100644
--- a/cekit/builders/osbs.py
+++ b/cekit/builders/osbs.py
@@ -19,7 +19,7 @@ from cekit.config import Config
 from cekit.builder import Builder
 from cekit.descriptor.resource import _PlainResource
 from cekit.errors import CekitError
-from cekit.tools import Chdir
+from cekit.tools import Chdir, copy_recursively
 
 LOGGER = logging.getLogger('cekit')
 CONFIG = Config()
@@ -86,6 +86,7 @@ class OSBSBuilder(Builder):
         super(OSBSBuilder, self).prepare()
 
         self.prepare_dist_git()
+        self.copy_to_dist_git()
 
     def prepare_dist_git(self):
         repository_key = self.generator.image.get('osbs', {}).get('repository', {})
@@ -128,31 +129,9 @@ class OSBSBuilder(Builder):
             self._rhpkg_set_url_repos = [x['url']['repository']
                                          for x in self.generator.image['packages']['set_url']]
 
-        self.update_osbs_image_source()
-
-    def update_osbs_image_source(self):
-        with Chdir(os.path.join(self.target, 'image')):
-            for obj in ["repos", "modules"]:
-                if os.path.exists(obj):
-                    shutil.copytree(obj, os.path.join(self.dist_git_dir, obj))
-            shutil.copy("Dockerfile",
-                        os.path.join(self.dist_git_dir, "Dockerfile"))
-            if os.path.exists("container.yaml"):
-                self._merge_container_yaml("container.yaml",
-                                           os.path.join(self.dist_git_dir, "container.yaml"))
-
-            for special_file in ["content_sets.yml", "fetch-artifacts-url.yaml", "help.md"]:
-                if os.path.exists(special_file):
-                    shutil.copy(special_file,
-                                os.path.join(self.dist_git_dir, special_file))
-
-        # Copy also every artifact
-        for artifact in self.artifacts:
-            shutil.copy(os.path.join(self.target,
-                                     'image',
-                                     artifact),
-                        os.path.join(self.dist_git_dir,
-                                     artifact))
+    def copy_to_dist_git(self):
+        LOGGER.debug("Copying files to dist-git '{}' directory".format(self.dist_git_dir))
+        copy_recursively(os.path.join(self.target, 'image'), self.dist_git_dir)
 
     def _merge_container_yaml(self, src, dest):
         # FIXME - this is temporary needs to be refactored to proper merging
@@ -245,7 +224,7 @@ class OSBSBuilder(Builder):
         cmd += ['call', '--python', 'buildContainer', '--kwargs']
 
         with Chdir(self.dist_git_dir):
-            self.dist_git.add()
+            self.dist_git.add(self.artifacts)
             self.update_lookaside_cache()
 
             if self.dist_git.stage_modified():
@@ -324,6 +303,7 @@ class DistGit(object):
 
     def __init__(self, output, source, repo, branch, noninteractive=False):
         self.output = output
+        self.source = source
         self.repo = repo
         self.branch = branch
         self.dockerfile = os.path.join(self.output, "Dockerfile")
@@ -378,17 +358,17 @@ class DistGit(object):
                 if d in git_files:
                     subprocess.check_output(["git", "rm", "-rf", d])
 
-    def add(self):
-        # Add new Dockerfile
-        subprocess.check_call(["git", "add", "Dockerfile"])
+    def add(self, artifacts):
+        LOGGER.debug("Adding files to git stage...")
 
-        for f in ["container.yaml", "content_sets.yml", "fetch-artifacts-url.yaml", "help.md"]:
-            if os.path.exists(f):
-                subprocess.check_call(["git", "add", f])
+        for obj in os.listdir('.'):
+            if obj in artifacts:
+                LOGGER.debug("Skipping staging '{}' in git because it is an artifact".format(obj))
+                continue
 
-        for d in ["repos", "modules"]:
-            # we probably do not care about non existing files and other errors here
-            subprocess.call(["git", "add", "--all", d])
+            LOGGER.debug("Staging '{}'...".format(obj))
+
+            subprocess.check_call(["git", "add", "--all", obj])
 
     def commit(self, commit_msg):
         if not commit_msg:
diff --git a/cekit/descriptor/image.py b/cekit/descriptor/image.py
index 37c8a9b..0264122 100644
--- a/cekit/descriptor/image.py
+++ b/cekit/descriptor/image.py
@@ -81,6 +81,10 @@ class Image(Descriptor):
         if not self.packages.manager:
             self.packages._descriptor['manager'] = 'yum'
 
+        # Default directory for supplementary files that should be copied to dist-git directory
+        if not self.osbs.extra_dir:
+            self.osbs._descriptor['extra_dir'] = 'osbs_extra'
+
     @property
     def name(self):
         return self.get('name')
diff --git a/cekit/descriptor/osbs.py b/cekit/descriptor/osbs.py
index 78c159d..0f7ba80 100644
--- a/cekit/descriptor/osbs.py
+++ b/cekit/descriptor/osbs.py
@@ -12,6 +12,8 @@ map:
       name: {type: str}
       branch: {type: str}
   configuration: {type: any}
+  extra_dir: {type: str}
+
 """)]
 
 configuration_schema = [yaml.safe_load("""
@@ -22,11 +24,14 @@ configuration_schema = [yaml.safe_load("""
 
 
 class Osbs(Descriptor):
-    """Object Representing OSBS configuration
+    """
+    Object Representing OSBS configuration
 
     Args:
-      descriptor - yaml containing Osbs object
+      descriptor: dictionary object containing OSBS configuration
+      descriptor_path: path to descriptor file
     """
+
     def __init__(self, descriptor, descriptor_path):
         self.schemas = osbs_schema
         self.descriptor_path = descriptor_path
@@ -55,6 +60,15 @@ class Osbs(Descriptor):
     def configuration(self):
         return self.get('configuration')
 
+    @property
+    def extra_dir(self):
+        return self.get('extra_dir')
+
+    @extra_dir.setter
+    def extra_dir(self, value):
+        self._descriptor['extra_dir'] = value
+
+
 class Configuration(Descriptor):
     """Internal object represeting OSBS configuration subObject
 
diff --git a/cekit/generator/base.py b/cekit/generator/base.py
index 3029050..30011fc 100644
--- a/cekit/generator/base.py
+++ b/cekit/generator/base.py
@@ -2,6 +2,7 @@
 
 import logging
 import os
+import re
 import platform
 import shutil
 
@@ -81,6 +82,9 @@ class Generator(object):
         Initializes the image object.
         """
 
+        LOGGER.debug("Removing old target directory")
+        shutil.rmtree(self.target, ignore_errors=True)
+
         # Read the main image descriptor and create an Image object from it
         descriptor = tools.load_descriptor(self._descriptor_path)
         self.image = Image(descriptor, os.path.dirname(os.path.abspath(self._descriptor_path)))
@@ -116,6 +120,11 @@ class Generator(object):
         # was used to build the image
         image_labels.append(Label({'name': 'io.cekit.version', 'value': cekit_version}))
 
+        for label in image_labels:
+            if len(label.value) > 128:
+                # breaks the line each time it reaches 128 characters
+                label.value = "\\\n".join(re.findall("(?s).{,128}", label.value))[:]
+
         # If we define the label in the image descriptor
         # we should *not* override it with value from
         # the root's key
diff --git a/cekit/generator/osbs.py b/cekit/generator/osbs.py
index 4bb3261..0a5f22a 100644
--- a/cekit/generator/osbs.py
+++ b/cekit/generator/osbs.py
@@ -6,7 +6,7 @@ import yaml
 from cekit.config import Config
 from cekit.descriptor.resource import _PlainResource
 from cekit.generator.base import Generator
-from cekit.tools import get_brew_url
+from cekit.tools import get_brew_url, copy_recursively
 
 logger = logging.getLogger('cekit')
 config = Config()
@@ -22,6 +22,18 @@ class OSBSGenerator(Generator):
 
         self._prepare_container_yaml()
 
+    def generate(self, builder):
+        # If extra directory exists (by default named 'osbs_extra') next to
+        # the image descriptor, copy it contents to the target directory.
+        #
+        # https://github.com/cekit/cekit/issues/394
+        copy_recursively(
+            os.path.join(os.path.dirname(self._descriptor_path), self.image.osbs.extra_dir),
+            os.path.join(self.target, 'image')
+        )
+
+        super(OSBSGenerator, self).generate(builder)
+
     def _prepare_content_sets(self, content_sets):
         content_sets_f = os.path.join(self.target, 'image', 'content_sets.yml')
 
diff --git a/cekit/tools.py b/cekit/tools.py
index 30a4387..cba0a21 100644
--- a/cekit/tools.py
+++ b/cekit/tools.py
@@ -1,5 +1,6 @@
 import logging
 import os
+import shutil
 import subprocess
 import sys
 
@@ -129,6 +130,39 @@ def get_brew_url(md5):
     return url
 
 
+def copy_recursively(source_directory, destination_directory):
+    """
+    Copies contents of a directory to selected target location (also a directory).
+    the specific source file to destination.
+
+    If the source directory contains a directory, it will copy all the content recursively.
+    Symlinks are preserved (not followed).
+
+    The destination directory tree will be created if it does not exist.
+    """
+
+    # If the source directory does not exists, return
+    if not os.path.isdir(source_directory):
+        return
+
+    # Iterate over content in the source directory
+    for name in os.listdir(source_directory):
+        src = os.path.join(source_directory, name)
+        dst = os.path.join(destination_directory, name)
+
+        LOGGER.debug("Copying '{}' to '{}'...".format(src, dst))
+
+        if not os.path.isdir(os.path.dirname(dst)):
+            os.makedirs(os.path.dirname(dst))
+
+        if os.path.islink(src):
+            os.symlink(os.readlink(src), dst)
+        elif os.path.isdir(src):
+            shutil.copytree(src, dst, symlinks=True)
+        else:
+            shutil.copy2(src, dst)
+
+
 class Chdir(object):
     """ Context manager for changing the current working directory """
 
diff --git a/docs/descriptor/includes/image/osbs.rst b/docs/descriptor/includes/image/osbs.rst
index 91a5c8a..30ca64a 100644
--- a/docs/descriptor/includes/image/osbs.rst
+++ b/docs/descriptor/includes/image/osbs.rst
@@ -26,6 +26,30 @@ It contains two main keys:
                 compose:
                     pulp_repos: true
 
+OSBS extra directory
+^^^^^^^^^^^^^^^^^^^^^
+
+Key
+    ``extra_dir``
+Required
+    No
+
+If a directory name specified by ``extra_dir`` key will be found next to the image descriptor, the contents of this directory
+will be copied into the target directory and later to the dist-git directory.
+
+Symbolic links are preserved (not followed).
+
+Copying files is done before generation, which means that files from the extra directory can be overridden
+in the :ref:`generation phase<handbook/building/build-process:Generating required files>` .
+
+.. note::
+    If you do not specify this key in image descriptor, the default value of ``osbs_extra`` will be used.
+
+.. code-block:: yaml
+
+    osbs:
+        extra_dir: custom-files
+
 OSBS repository
 ^^^^^^^^^^^^^^^^
 


Test patch:
diff --git a/tests/test_builder.py b/tests/test_builder.py
index a7dad3f..74334f5 100644
--- a/tests/test_builder.py
+++ b/tests/test_builder.py
@@ -136,7 +136,7 @@ def test_merge_container_yaml_limit_arch(mocker, tmpdir):
 
 
 class DistGitMock(object):
-    def add(self):
+    def add(self, artifacts):
         pass
 
     def stage_modified(self):
@@ -414,7 +414,7 @@ def test_osbs_copy_artifacts_to_dist_git(mocker, tmpdir, artifact, src, target):
 
     mocker.patch('cekit.tools.DependencyHandler.handle')
     mocker.patch('cekit.descriptor.resource.Resource.copy')
-    copy_mock = mocker.patch('cekit.builders.osbs.shutil.copy')
+    copy_mock = mocker.patch('cekit.builders.osbs.shutil.copy2')
 
     dist_git_class = mocker.patch('cekit.builders.osbs.DistGit')
     dist_git_class.return_value = DistGitMock()
@@ -448,10 +448,10 @@ def test_osbs_copy_artifacts_to_dist_git(mocker, tmpdir, artifact, src, target):
     dist_git_class.assert_called_once_with(os.path.join(
         str(tmpdir), 'osbs', 'repo'), str(tmpdir), 'repo', 'branch')
 
-    calls = [mocker.call('Dockerfile', os.path.join(str(tmpdir), 'osbs/repo/Dockerfile')),
-             mocker.call(os.path.join(str(tmpdir), src), os.path.join(str(tmpdir), target))]
-
-    copy_mock.assert_has_calls(calls)
+    copy_mock.assert_has_calls([
+        mocker.call(os.path.join(str(tmpdir), 'image', 'Dockerfile'),
+                    os.path.join(str(tmpdir), 'osbs/repo/Dockerfile'))
+    ])
 
 
 def test_docker_builder_defaults():
diff --git a/tests/test_dockerfile.py b/tests/test_dockerfile.py
index f319903..50cd8fe 100644
--- a/tests/test_dockerfile.py
+++ b/tests/test_dockerfile.py
@@ -89,6 +89,7 @@ def test_dockerfile_rendering(tmpdir, mocker, name, desc_part, exp_regex):
                          ids=print_test_name)
 def test_dockerfile_rendering_tech_preview(tmpdir, mocker, desc_part, exp_regex):
     mocker.patch('cekit.builders.osbs.OSBSBuilder.prepare_dist_git')
+    mocker.patch('cekit.builders.osbs.OSBSBuilder.copy_to_dist_git')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.dependencies')
     target = str(tmpdir.mkdir('target'))
 
@@ -119,6 +120,7 @@ def test_dockerfile_docker_odcs_rpm(tmpdir, mocker):
                  'state': 2, 'result_repofile': 'url'})
     mocker.patch.object(Repository, 'fetch')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.prepare_dist_git')
+    mocker.patch('cekit.builders.osbs.OSBSBuilder.copy_to_dist_git')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.dependencies')
 
     target = str(tmpdir.mkdir('target'))
@@ -155,6 +157,7 @@ def test_dockerfile_osbs_odcs_pulp(tmpdir, mocker):
                  'state': 2, 'result_repofile': 'url'})
     mocker.patch.object(Repository, 'fetch')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.prepare_dist_git')
+    mocker.patch('cekit.builders.osbs.OSBSBuilder.copy_to_dist_git')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.dependencies')
     config.cfg['common'] = {'redhat': True}
 
@@ -180,6 +183,7 @@ def test_dockerfile_osbs_odcs_pulp_no_redhat(tmpdir, mocker):
                  'state': 2, 'result_repofile': 'url'})
     mocker.patch.object(Repository, 'fetch')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.prepare_dist_git')
+    mocker.patch('cekit.builders.osbs.OSBSBuilder.copy_to_dist_git')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.dependencies')
     config.cfg['common'] = {'redhat': False}
 
@@ -204,6 +208,7 @@ def test_dockerfile_osbs_id_redhat_false(tmpdir, mocker):
                  'state': 2, 'result_repofile': 'url'})
     mocker.patch.object(Repository, 'fetch')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.prepare_dist_git')
+    mocker.patch('cekit.builders.osbs.OSBSBuilder.copy_to_dist_git')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.dependencies')
     target = str(tmpdir.mkdir('target'))
     desc_part = {'packages': {'repositories': [{'name': 'foo',
@@ -223,6 +228,7 @@ def test_dockerfile_osbs_url_only(tmpdir, mocker):
                  'state': 2, 'result_repofile': 'url'})
     mocker.patch.object(Repository, 'fetch')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.prepare_dist_git')
+    mocker.patch('cekit.builders.osbs.OSBSBuilder.copy_to_dist_git')
     mocker.patch('cekit.builders.osbs.OSBSBuilder.dependencies')
     target = str(tmpdir.mkdir('target'))
     desc_part = {'packages': {'repositories': [{'name': 'foo',
diff --git a/tests/test_integ_builder_osbs.py b/tests/test_integ_builder_osbs.py
index 3ae9568..2236633 100644
--- a/tests/test_integ_builder_osbs.py
+++ b/tests/test_integ_builder_osbs.py
@@ -90,17 +90,6 @@ def run_osbs(descriptor, image_dir, mocker):
                                  'osbs'])
 
 
-def run_cekit(cwd,
-              parameters=['build', '--dry-run', 'docker'],
-              message=None):
-    with Chdir(cwd):
-        result = CliRunner().invoke(cli, parameters, catch_exceptions=False)
-        if message:
-            assert message in result.output
-
-        return result
-
-
 def test_osbs_builder_kick_build_without_push(tmpdir, mocker, caplog):
     """
     Does not push sources to dist-git. This is the case when the
@@ -124,7 +113,7 @@ def test_osbs_builder_kick_build_without_push(tmpdir, mocker, caplog):
 
     mock_check_call.assert_has_calls(
         [
-            mocker.call(['git', 'add', 'Dockerfile']),
+            mocker.call(['git', 'add', '--all', 'Dockerfile']),
         ])
 
     assert "No changes made to the code, committing skipped" in caplog.text
@@ -154,7 +143,7 @@ def test_osbs_builder_kick_build_with_push(tmpdir, mocker, caplog):
 
     mock_check_call.assert_has_calls(
         [
-            mocker.call(['git', 'add', 'Dockerfile']),
+            mocker.call(['git', 'add', '--all', 'Dockerfile']),
             mocker.call(['git', 'commit', '-q', '-m',
                          'Sync with path, commit 3b9283cb26b35511517ff5c0c3e11f490cba8feb']),
             mocker.call(['git', 'push', '-q', 'origin', 'branch'])
@@ -186,10 +175,108 @@ def test_osbs_builder_add_help_file(tmpdir, mocker, caplog):
     assert os.path.exists(str(repo_dir.join('Dockerfile'))) is True
     assert os.path.exists(str(repo_dir.join('help.md'))) is True
 
-    mock_check_call.assert_has_calls(
-        [
-            mocker.call(['git', 'add', 'Dockerfile']),
-            mocker.call(['git', 'add', 'help.md']),
-        ])
+    calls = [
+        mocker.call(['git', 'add', '--all', 'Dockerfile']),
+        mocker.call(['git', 'add', '--all', 'help.md']),
+    ]
+
+    mock_check_call.assert_has_calls(calls, any_order=True)
+
+    assert len(mock_check_call.mock_calls) == len(calls)
+    assert "Image was built successfully in OSBS!" in caplog.text
+
+
+# https://github.com/cekit/cekit/issues/394
+def test_osbs_builder_add_extra_files(tmpdir, mocker, caplog):
+    """
+    Checks if content of the 'osbs_extra' directory content is copied to dist-git
+    """
+
+    caplog.set_level(logging.DEBUG, logger="cekit")
+
+    source_dir = tmpdir.mkdir('source')
+    repo_dir = source_dir.mkdir('osbs').mkdir('repo')
+    dist_dir = source_dir.mkdir('osbs_extra')
+
+    dist_dir.join('file_a').write_text(u'Some content', 'utf8')
+    dist_dir.join('file_b').write_text(u'Some content', 'utf8')
+    dist_dir.mkdir('child').join('other').write_text(u'Some content', 'utf8')
+
+    os.symlink('/etc', str(dist_dir.join('a_symlink')))
+
+    mocker.patch.object(subprocess, 'call', return_value=0)
+    mock_check_call = mocker.patch.object(subprocess, 'check_call')
+
+    run_osbs(image_descriptor, str(source_dir), mocker)
+
+    assert os.path.exists(str(repo_dir.join('Dockerfile'))) is True
+    assert os.path.exists(str(repo_dir.join('file_a'))) is True
+    assert os.path.exists(str(repo_dir.join('file_b'))) is True
+
+    calls = [
+        mocker.call(['git', 'add', '--all', 'file_b']),
+        mocker.call(['git', 'add', '--all', 'file_a']),
+        mocker.call(['git', 'add', '--all', 'Dockerfile']),
+        mocker.call(['git', 'add', '--all', 'child']),
+        mocker.call(['git', 'add', '--all', 'a_symlink'])
+    ]
+
+    mock_check_call.assert_has_calls(calls, any_order=True)
+
+    assert len(mock_check_call.mock_calls) == len(calls)
+    assert "Image was built successfully in OSBS!" in caplog.text
+    assert "Copying files to dist-git '{}' directory".format(str(repo_dir)) in caplog.text
+    assert "Copying 'target/image/file_b' to '{}'...".format(
+        os.path.join(str(repo_dir), 'file_b')) in caplog.text
+    assert "Staging 'file_a'..." in caplog.text
+    assert "Staging 'a_symlink'..." in caplog.text
+
+
+# https://github.com/cekit/cekit/issues/394
+def test_osbs_builder_add_extra_files_from_custom_dir(tmpdir, mocker, caplog):
+    """
+    Checks if content of the custom specified 'dist' directory content is copied to dist-git
+    """
+
+    caplog.set_level(logging.DEBUG, logger="cekit")
+
+    source_dir = tmpdir.mkdir('source')
+    repo_dir = source_dir.mkdir('osbs').mkdir('repo')
+    dist_dir = source_dir.mkdir('dist')
+
+    dist_dir.join('file_a').write_text(u'Some content', 'utf8')
+    dist_dir.join('file_b').write_text(u'Some content', 'utf8')
+    dist_dir.mkdir('child').join('other').write_text(u'Some content', 'utf8')
+
+    os.symlink('/etc', str(dist_dir.join('a_symlink')))
+
+    mocker.patch.object(subprocess, 'call', return_value=0)
+    mock_check_call = mocker.patch.object(subprocess, 'check_call')
+
+    descriptor = image_descriptor.copy()
+
+    descriptor['osbs']['extra_dir'] = 'dist'
+
+    run_osbs(descriptor, str(source_dir), mocker)
+
+    assert os.path.exists(str(repo_dir.join('Dockerfile'))) is True
+    assert os.path.exists(str(repo_dir.join('file_a'))) is True
+    assert os.path.exists(str(repo_dir.join('file_b'))) is True
+
+    calls = [
+        mocker.call(['git', 'add', '--all', 'file_b']),
+        mocker.call(['git', 'add', '--all', 'file_a']),
+        mocker.call(['git', 'add', '--all', 'Dockerfile']),
+        mocker.call(['git', 'add', '--all', 'child']),
+        mocker.call(['git', 'add', '--all', 'a_symlink'])
+    ]
+
+    mock_check_call.assert_has_calls(calls, any_order=True)
 
+    assert len(mock_check_call.mock_calls) == len(calls)
     assert "Image was built successfully in OSBS!" in caplog.text
+    assert "Copying files to dist-git '{}' directory".format(str(repo_dir)) in caplog.text
+    assert "Copying 'target/image/file_b' to '{}'...".format(
+        os.path.join(str(repo_dir), 'file_b')) in caplog.text
+    assert "Staging 'file_a'..." in caplog.text
+    assert "Staging 'a_symlink'..." in caplog.text
diff --git a/tests/test_unit_generator_docker.py b/tests/test_unit_generator_docker.py
index 69c863f..bb2adf6 100644
--- a/tests/test_unit_generator_docker.py
+++ b/tests/test_unit_generator_docker.py
@@ -8,10 +8,12 @@ import os
 from contextlib import contextmanager
 
 import pytest
+import yaml
 
 from cekit.config import Config
 from cekit.errors import CekitError
 from cekit.generator.docker import DockerGenerator
+from cekit.descriptor import Image
 
 
 odcs_fake_resp = {
@@ -104,6 +106,22 @@ def test_prepare_content_sets_should_not_fail_when_cs_is_empty(tmpdir):
             assert generator._prepare_content_sets({}) is False
 
 
+def test_large_labels_should_break_lines(tmpdir):
+    image = Image(yaml.safe_load("""
+    from: foo
+    name: test/foo
+    version: 1.9
+    labels:
+      - name: 'the.large.label'
+        value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec pretium finibus lorem vitae pellentesque. Maecenas tincidunt amet.
+    """), 'foo')
+    with docker_generator(tmpdir) as generator:
+        generator.image = image
+        with cekit_config(redhat=True):
+            generator.add_build_labels()
+            assert image.labels[0].value == "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec pretium finibus lorem vitae pellentesque. Maecenas tincidunt amet\\\n.\\\n"
+
+
 def test_prepare_content_sets_should_fail_when_cs_are_note_defined_for_current_platform(tmpdir, mocker):
     mocker.patch('cekit.generator.base.platform.machine',
                  return_value="current_platform")