Repository: cekit/cekit

Issue:
additional arguments to package manager (specifically microdnf and --setopt=install_weak_deps=0)
**Describe the solution you'd like**

I would like the invocations to microdnf generated by cekit in response to `packages:` values to include the extra command-line argument `--setopt=install_weak_deps=0`.

This causes microdnf to not install recommended or suggested RPMs ("weak" dependencies), only traditional "requires" ones. This will result in generally smaller images.

The only way to configure microdnf to do this is via command line, it is not possible to influence it via configuration file or similar: https://github.com/rpm-software-management/microdnf/blob/8a1f065966362afb96e95d177d829271a3ee3ab7/dnf/dnf-main.c#L176

**Additional context**

the last respin of the openjdk containers grew in size by 6% as a result of transitive weak package dependencies.

Discussion:
jmtd: I've just tested this branch with a build of [this exact image descriptor](https://github.com/jboss-container-images/openjdk/blob/aa4a4088ed51bd2df32bc381e53ffe3eaca80f98/ubi8-openjdk-8.yaml). It built fine, and the resulting image was much smaller (approx 25% ish) due to the number of RPMs that were not installed (as desired)

Patch:
diff --git a/cekit/descriptor/base.py b/cekit/descriptor/base.py
index 8c07fda..52a1b77 100644
--- a/cekit/descriptor/base.py
+++ b/cekit/descriptor/base.py
@@ -77,9 +77,9 @@ class Descriptor(collectionsAbc.MutableMapping):
 
     def merge(self, descriptor):
         """ Merges two descriptors in a way, that arrays are appended
-        and duplicit values are kept
+        and duplicate values are kept
         Args:
-          descriptor - a cekit descritor
+          descriptor - a cekit descriptor
         """
         try:
             _merge_descriptors(self, descriptor)
diff --git a/cekit/descriptor/execute.py b/cekit/descriptor/execute.py
index 84cc23c..a0a472e 100644
--- a/cekit/descriptor/execute.py
+++ b/cekit/descriptor/execute.py
@@ -26,12 +26,8 @@ class Execute(Descriptor):
         descriptor['module_name'] = module_name
 
         if 'name' not in descriptor:
-            # Generated name
             descriptor['name'] = "{}/{}".format(module_name, descriptor['script'])
 
-            logger.debug("No value found for 'name' key in the execute section of the '{}' module; using auto-generated value: '{}'".format(
-                module_name, descriptor['name']))
-
     @property
     def name(self):
         return self.get('name')
diff --git a/cekit/descriptor/image.py b/cekit/descriptor/image.py
index 4a9d93b..e576f63 100644
--- a/cekit/descriptor/image.py
+++ b/cekit/descriptor/image.py
@@ -19,7 +19,6 @@ map:
   description: {type: text}
   labels: {type: any}
   envs:  {type: any}
-  execute: {type: any}
   ports: {type: any}
   run: {type: any}
   artifacts: {type: any}
diff --git a/cekit/descriptor/module.py b/cekit/descriptor/module.py
index befa799..b8fbce4 100644
--- a/cekit/descriptor/module.py
+++ b/cekit/descriptor/module.py
@@ -1,6 +1,8 @@
 from cekit.descriptor import Image, Execute
 from cekit.descriptor.image import get_image_schema
 
+overrides_schema = get_image_schema()
+overrides_schema['map']['execute'] = {'type': 'any'}
 
 class Module(Image):
     """Represents a module.
@@ -12,7 +14,7 @@ class Module(Image):
     def __init__(self, descriptor, path, artifact_dir):
         self._artifact_dir = artifact_dir
         self.path = path
-        self.schema = get_image_schema().copy()
+        self.schema = overrides_schema.copy()
         # calling Descriptor constructor only here (we don't want Image() to mess with schema)
         super(Image, self).__init__(descriptor)
         self.skip_merging = ['description',
diff --git a/cekit/descriptor/volume.py b/cekit/descriptor/volume.py
index 622b6ef..3aaf389 100644
--- a/cekit/descriptor/volume.py
+++ b/cekit/descriptor/volume.py
@@ -25,6 +25,4 @@ class Volume(Descriptor):
         self.schema = volume_schema
         super(Volume, self).__init__(descriptor)
         if 'name' not in self._descriptor:
-            logger.warning("No value found for 'name' in 'volume'; using auto-generated value of '{}'".
-                           format(os.path.basename(self._descriptor['path'])))
             self._descriptor['name'] = os.path.basename(self._descriptor['path'])
diff --git a/cekit/template_helper.py b/cekit/template_helper.py
index db26b4c..e839185 100644
--- a/cekit/template_helper.py
+++ b/cekit/template_helper.py
@@ -87,3 +87,24 @@ class TemplateHelper(object):
     def cachito(self, image):
         if image.get('osbs', {}).get('configuration', {}).get('container', {}).get('remote_source'):
             return True
+
+    def package_manager_flags(self, pkg_mgr):
+        default = "--setopt=tsflags=nodocs"
+        if "apk" in pkg_mgr:
+            return ""
+        elif "microdnf" in pkg_mgr:
+            return "--setopt=install_weak_deps=0 " + default
+        else:
+            return default
+
+    def package_manager_install(self, pkg_mgr):
+        if "apk" in pkg_mgr:
+            return "add"
+        else:
+            return "install -y"
+
+    def package_manager_query(self, pkg_mgr):
+        if "apk" in pkg_mgr:
+            return "apk info -e"
+        else:
+            return "rpm -q"
diff --git a/cekit/templates/template.jinja b/cekit/templates/template.jinja
index 30d6eb5..8abd069 100644
--- a/cekit/templates/template.jinja
+++ b/cekit/templates/template.jinja
@@ -30,7 +30,7 @@ rm{% for repo in repositories %} /etc/yum.repos.d/{{ repo.filename }}{% endfor %
  #}
 {%- macro repo_install(pkg_manager, rpm) -%}
     {% if pkg_manager in helper.SUPPORTED_PACKAGE_MANAGERS %}
-{{ pkg_manager }} --setopt=tsflags=nodocs install -y {{ rpm }}
+{{ pkg_manager }} {{ helper.package_manager_flags(pkg_manager) }} install -y {{ rpm }}
     {% endif %}
 {%- endmacro -%}
 
@@ -39,17 +39,12 @@ rm{% for repo in repositories %} /etc/yum.repos.d/{{ repo.filename }}{% endfor %
  #}
 {%- macro pkg_install(pkg_manager, packages) -%}
     {% if pkg_manager in helper.SUPPORTED_PACKAGE_MANAGERS %}
-        {% if pkg_manager == 'apk' %}
-{{ pkg_manager }} add {%- for package in packages %} {{ package }}{% endfor %} \
-        && apk info -e {%- for package in packages %} {{ package }}{% endfor %}
-        {% else %}
-{{ pkg_manager }} --setopt=tsflags=nodocs install -y {%- for package in packages %} {{ package }}{% endfor %} \
+{{ pkg_manager }} {{ helper.package_manager_flags(pkg_manager) }} {{ helper.package_manager_install(pkg_manager) }} {%- for package in packages %} {{ package }}{% endfor %} \
     {% if pkg_manager == 'microdnf' %}
             && {{ pkg_manager }} clean all \
     {% endif %}
-            && rpm -q{% for package in packages %} {{ package }}{% endfor %}
+            && {{ helper.package_manager_query(pkg_manager) }}{% for package in packages %} {{ package }}{% endfor %}
     {% endif %}
-        {% endif %}
 {%- endmacro -%}
 
 {#
diff --git a/docs/descriptor/includes/packages.rst b/docs/descriptor/includes/packages.rst
index 106b35c..53ddbe7 100644
--- a/docs/descriptor/includes/packages.rst
+++ b/docs/descriptor/includes/packages.rst
@@ -71,6 +71,9 @@ Currently available options are ``yum``, ``dnf``, ``microdnf`` and ``apk``.
     The default ``yum`` value will work fine on Fedora and RHEL images because
     OS maintains a symlink to the proper package manager.
 
+.. note::
+    For ``yum``, ``dnf`` and ``microdnf`` the flag ``--setopt=tsflags=nodocs`` is automatically added. For ``microdnf``, the flag ``--setopt=install_weak_deps=0`` is also added.
+
 .. code-block:: yaml
 
     packages:
diff --git a/docs/handbook/ci.rst b/docs/handbook/ci.rst
new file mode 100644
index 0000000..05f6cd0
--- /dev/null
+++ b/docs/handbook/ci.rst
@@ -0,0 +1,37 @@
+CI Integration
+==============
+
+It is also possible to integrate CEKit into your continuous integration via a GitHub Action. The action is available  `here <https://github.com/manusa/actions-setup-cekit>`_.
+
+An example of its usage (from `jkube-images <https://github.com/jkubeio/jkube-images/blob/main/.github/workflows/build-images.yml>`_ is:
+
+.. code-block:: yaml
+
+    name: Build Images
+
+    on:
+      pull_request:
+      push:
+        branches:
+          - main
+
+    env:
+      TAG: latest
+
+    jobs:
+      build-images:
+        name: Build Images
+        runs-on: ubuntu-latest
+        strategy:
+          fail-fast: false
+          matrix:
+            image: ['jkube-java-binary-s2i', 'jkube-jetty9-binary-s2i', 'jkube-karaf-binary-s2i', 'jkube-tomcat9-binary-s2i']
+        steps:
+          - name: Checkout
+            uses: actions/checkout@v2
+          - name: Install CEKit
+            uses: manusa/actions-setup-cekit@v1.1.1
+          - name: Build java-binary-s2i
+            run: |
+              echo "Building quay.io/jkube/${{ matrix.image }}:${TAG}"
+              cekit --descriptor ${{ matrix.image }}.yaml build docker --tag="quay.io/jkube/${{ matrix.image }}:${TAG}"
diff --git a/docs/handbook/index.rst b/docs/handbook/index.rst
index db8801c..70caa0e 100644
--- a/docs/handbook/index.rst
+++ b/docs/handbook/index.rst
@@ -16,3 +16,4 @@ This chapter will guide you through the CEKit usage covering all important topic
     overrides
     configuration
     redhat
+    ci


Test patch:
diff --git a/tests/test_dockerfile.py b/tests/test_dockerfile.py
index eaa6f56..fedd5a1 100644
--- a/tests/test_dockerfile.py
+++ b/tests/test_dockerfile.py
@@ -1,6 +1,5 @@
 import os
 import re
-import subprocess
 
 import pytest
 import yaml
@@ -10,7 +9,6 @@ from cekit.config import Config
 from cekit.descriptor import Repository
 from cekit.version import version as cekit_version
 from cekit.tools import Chdir
-from cekit.template_helper import TemplateHelper
 
 from click.testing import CliRunner
 
@@ -55,13 +53,6 @@ def print_test_name(value):
                              'example': 'example_value',
                              'description': 'This is a description'}]},
      r' \\\s+COMBINED_ENV=\"set_value\" \\\s+JBOSS_IMAGE_NAME=\"testimage\" \\\s+JBOSS_IMAGE_VERSION=\"1\" \n'),
-    ('test_execute',
-     {'execute': [{'script': 'foo_script'}]},
-     r'.*RUN [ "bash", "-x", "/tmp/scripts/testimage/foo_script" ].*'),
-    ('test_execute_user',
-     {'execute': [{'script': 'bar_script',
-                   'user': 'bar_user'}]},
-     r'.*USER bar_user\n\s+RUN [ "bash", "-x", "/tmp/scripts/testimage/foo_script" ].*'),
     ('test_cekit_label_version',
      {},
      r'.*io.cekit.version="%s".*' % cekit_version)],
@@ -124,8 +115,8 @@ def test_dockerfile_docker_odcs_rpm_microdnf(tmpdir, mocker):
                               'install': ['a', 'b']}}
 
     generate(target, ['build', '--dry-run', 'podman'], desc_part)
-    regex_dockerfile(target, 'RUN microdnf --setopt=tsflags=nodocs install -y foo-repo.rpm')
-    regex_dockerfile(target, 'RUN microdnf --setopt=tsflags=nodocs install -y a b')
+    regex_dockerfile(target, 'RUN microdnf --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install -y foo-repo.rpm')
+    regex_dockerfile(target, 'RUN microdnf --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install -y a b')
     regex_dockerfile(target, 'rpm -q a b')
 
 
@@ -289,8 +280,8 @@ def test_dockerfile_custom_package_manager_with_overrides(tmpdir):
                                       'install': ['a']},
                          'osbs': {'repository': {'name': 'repo_name', 'branch': 'branch_name'}}
                          })
-    regex_dockerfile(target, 'RUN microdnf --setopt=tsflags=nodocs install -y foo-repo.rpm')
-    regex_dockerfile(target, 'RUN microdnf --setopt=tsflags=nodocs install -y a b')
+    regex_dockerfile(target, 'RUN microdnf --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install -y foo-repo.rpm')
+    regex_dockerfile(target, 'RUN microdnf --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install -y a b')
     regex_dockerfile(target, 'rpm -q a')
     regex_dockerfile(target, 'RUN microdnf clean all')
 
@@ -323,8 +314,8 @@ def test_dockerfile_osbs_odcs_rpm_microdnf(tmpdir):
                                       'install': ['a']},
                          'osbs': {'repository': {'name': 'repo_name', 'branch': 'branch_name'}}
                          })
-    regex_dockerfile(target, 'RUN microdnf --setopt=tsflags=nodocs install -y foo-repo.rpm')
-    regex_dockerfile(target, 'RUN microdnf --setopt=tsflags=nodocs install -y a')
+    regex_dockerfile(target, 'RUN microdnf --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install -y foo-repo.rpm')
+    regex_dockerfile(target, 'RUN microdnf --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install -y a')
     regex_dockerfile(target, 'rpm -q a')
 
 
@@ -340,9 +331,12 @@ def test_supported_package_managers(tmpdir, manager):
                                       'install': ['a']},
                          'osbs': {'repository': {'name': 'repo_name', 'branch': 'branch_name'}}
                          })
+    flags = "--setopt=tsflags=nodocs"
+    if 'microdnf' in manager:
+        flags = "--setopt=install_weak_deps=0 " + flags
     regex_dockerfile(
-        target, "RUN {} --setopt=tsflags=nodocs install -y foo-repo.rpm".format(manager))
-    regex_dockerfile(target, "RUN {} --setopt=tsflags=nodocs install -y a".format(manager))
+        target, "RUN {} {} install -y foo-repo.rpm".format(manager, flags))
+    regex_dockerfile(target, "RUN {} {} install -y a".format(manager, flags))
     regex_dockerfile(target, 'rpm -q a')
 
 
@@ -360,7 +354,7 @@ def test_supported_package_managers_apk(tmpdir, caplog):
                                   'rpm': 'foo-repo.rpm'}]
             }
         })
-    regex_dockerfile(target, "RUN apk add a")
+    regex_dockerfile(target, "RUN apk  add a")
     regex_dockerfile(target, "apk info -e a")
     assert "Package manager apk does not support defining repositories, skipping all repositories" in caplog.text
 
diff --git a/tests/test_unit_descriptor.py b/tests/test_unit_descriptor.py
index 4ccfced..64a69c1 100644
--- a/tests/test_unit_descriptor.py
+++ b/tests/test_unit_descriptor.py
@@ -54,8 +54,6 @@ def test_volume_name(caplog):
 """))
     assert volume['name'] == 'a'
     assert volume['path'] == '/tmp/a'
-    assert "No value found for 'name' in 'volume'; using auto-generated value of 'a'" \
-           in caplog.text
 
 
 def test_osbs():
@@ -238,3 +236,16 @@ def test_image_modules_git_repo(caplog):
     assert image['name'] == 'test/foo'
     assert "No value found for 'name' in '{\"git\": {\"ref\": \"release-3.1.0\", \"url\": \"https://github.com/company/foobar-project-modules\"}}' artifact; using auto-generated value of 'foobar-project-modules'" \
            in caplog.text
+
+
+def test_image_descriptor_with_execute(caplog):
+    with pytest.raises(CekitError) as excinfo:
+        image = Image(yaml.safe_load("""
+        from: foo
+        name: test/foo
+        version: 1.9
+        execute:
+              - script: build.sh
+        """), 'foo')
+
+    assert "Cannot validate schema: Image" in excinfo.value.message
diff --git a/tests/test_validate.py b/tests/test_validate.py
index aa5f731..ec36628 100644
--- a/tests/test_validate.py
+++ b/tests/test_validate.py
@@ -493,7 +493,7 @@ def test_microdnf_clean_all_cmd_present(tmpdir):
                'podman'])
 
     required_matches = [
-        'RUN microdnf --setopt=tsflags=nodocs install -y package1 package2 \\',
+        'RUN microdnf --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install -y package1 package2 \\',
         '&& microdnf clean all \\',
         '&& rpm -q package1 package2'
     ]
@@ -1093,7 +1093,6 @@ def test_package_related_commands_packages_in_module(tmpdir, mocker):
     copy_repos(image_dir)
 
     img_desc = image_descriptor.copy()
-    img_desc['execute'] = [{'script': 'configure.sh', 'user': 'someuser'}]
     img_desc['modules']['install'] = [{'name': 'packages_module'}, {'name': 'packages_module_1'}]
     img_desc['modules']['repositories'] = [{'name': 'modules',
                                             'path': 'tests/modules/repo_packages'}]