[libvirt-jenkins-ci PATCH] guests: allow for container image inheritance

Daniel P. Berrangé berrange at redhat.com
Tue Mar 31 14:28:20 UTC 2020


Currently when creating a Dockerfile for a container, we include the
full set of base packages, along with the packages for the project
itself. If building a Perl binding, this would require us to install
the base package, libvirt packages and Perl packages. With the use
of the "--inherit libvirt-fedora-30" arg, it is possible to have a
dockerfile that only adds the Perl packages, getting everything
else from the parent image.

For example, a full Dockerfile for libvirt-go would thus be:

  FROM libvirt-centos-7:latest

  RUN yum install -y \
          golang && \
      yum autoremove -y && \
      yum clean all -y

Note there is no need to set ENV either, as that's inherited from the
parent container.

Signed-off-by: Daniel P. Berrangé <berrange at redhat.com>
---
 guests/lcitool | 108 ++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 98 insertions(+), 10 deletions(-)

diff --git a/guests/lcitool b/guests/lcitool
index 689a8cf..b3afb6a 100755
--- a/guests/lcitool
+++ b/guests/lcitool
@@ -396,6 +396,12 @@ class Application:
                 help="target architecture for cross compiler",
             )
 
+        def add_inherit_arg(parser):
+            parser.add_argument(
+                "-i", "--inherit",
+                help="inherit from an intermediate container image",
+            )
+
         def add_wait_arg(parser):
             parser.add_argument(
                 "-w", "--wait",
@@ -442,6 +448,7 @@ class Application:
         add_hosts_arg(dockerfileparser)
         add_projects_arg(dockerfileparser)
         add_cross_arch_arg(dockerfileparser)
+        add_inherit_arg(dockerfileparser)
 
     def _execute_playbook(self, playbook, hosts, projects, git_revision):
         base = Util.get_base()
@@ -644,11 +651,11 @@ class Application:
         with open(keyfile, "r") as r:
             return r.read().rstrip()
 
-    def _dockerfile_build_varmap(self, facts, mappings, pip_mappings, projects, cross_arch):
+    def _dockerfile_build_varmap(self, facts, mappings, pip_mappings, projects, cross_arch, needbase):
         if facts["package_format"] == "deb":
-            varmap = self._dockerfile_build_varmap_deb(facts, mappings, pip_mappings, projects, cross_arch)
+            varmap = self._dockerfile_build_varmap_deb(facts, mappings, pip_mappings, projects, cross_arch, needbase)
         if facts["package_format"] == "rpm":
-            varmap = self._dockerfile_build_varmap_rpm(facts, mappings, pip_mappings, projects, cross_arch)
+            varmap = self._dockerfile_build_varmap_rpm(facts, mappings, pip_mappings, projects, cross_arch, needbase)
 
         varmap["package_manager"] = facts["package_manager"]
         varmap["cc"] = facts["cc"]
@@ -662,7 +669,7 @@ class Application:
 
         return varmap
 
-    def _dockerfile_build_varmap_deb(self, facts, mappings, pip_mappings, projects, cross_arch):
+    def _dockerfile_build_varmap_deb(self, facts, mappings, pip_mappings, projects, cross_arch, needbase):
         package_format = facts["package_format"]
         package_manager = facts["package_manager"]
         os_name = facts["os_name"]
@@ -682,7 +689,10 @@ class Application:
 
         # We need to add the base project manually here: the standard
         # machinery hides it because it's an implementation detail
-        for project in projects + ["base"]:
+        pkgprojects = projects
+        if needbase:
+            pkgprojects = projects + ["base"]
+        for project in pkgprojects:
             for package in self._projects.get_packages(project):
                 cross_policy = "native"
                 for key in cross_keys:
@@ -730,7 +740,7 @@ class Application:
 
         return varmap
 
-    def _dockerfile_build_varmap_rpm(self, facts, mappings, pip_mappings, projects, cross_arch):
+    def _dockerfile_build_varmap_rpm(self, facts, mappings, pip_mappings, projects, cross_arch, needbase):
         package_format = facts["package_format"]
         package_manager = facts["package_manager"]
         os_name = facts["os_name"]
@@ -744,7 +754,10 @@ class Application:
 
         # We need to add the base project manually here: the standard
         # machinery hides it because it's an implementation detail
-        for project in projects + ["base"]:
+        pkgprojects = projects
+        if needbase:
+            pkgprojects = projects + ["base"]
+        for project in pkgprojects:
             for package in self._projects.get_packages(project):
                 for key in keys:
                     if key in mappings[package]:
@@ -791,7 +804,7 @@ class Application:
 
         return varmap
 
-    def _dockerfile_format(self, facts, cross_arch, varmap):
+    def _dockerfile_base_format(self, facts, cross_arch, varmap):
         package_format = facts["package_format"]
         package_manager = facts["package_manager"]
         os_name = facts["os_name"]
@@ -931,6 +944,74 @@ class Application:
                 ENV CONFIGURE_OPTS "--host={cross_abi}"
             """).format(**varmap))
 
+    def _dockerfile_child_format(self, facts, cross_arch, inherit, varmap):
+        package_format = facts["package_format"]
+        package_manager = facts["package_manager"]
+        os_name = facts["os_name"]
+        os_version = facts["os_version"]
+
+        print("FROM {}".format(inherit))
+
+        commands = []
+
+        if package_format == "deb":
+            commands.extend([
+                "export DEBIAN_FRONTEND=noninteractive",
+                "{package_manager} update",
+                "{package_manager} install --no-install-recommends -y {pkgs}",
+                "{package_manager} autoremove -y",
+                "{package_manager} autoclean -y",
+            ])
+        elif package_format == "rpm":
+            commands.extend([
+                "{package_manager} install -y {pkgs}",
+            ])
+
+            # openSUSE doesn't seem to have a convenient way to remove all
+            # unnecessary packages, but CentOS and Fedora do
+            if os_name == "OpenSUSE":
+                commands.extend([
+                    "{package_manager} clean --all",
+                ])
+            else:
+                commands.extend([
+                    "{package_manager} autoremove -y",
+                    "{package_manager} clean all -y",
+                ])
+
+
+        script = "\nRUN " + (" && \\\n    ".join(commands)) + "\n"
+        sys.stdout.write(script.format(**varmap))
+
+        if cross_arch:
+            cross_commands = []
+
+            # Intentionally a separate RUN command from the above
+            # so that the common packages of all cross-built images
+            # share a Docker image layer.
+            if package_format == "deb":
+                cross_commands.extend([
+                    "export DEBIAN_FRONTEND=noninteractive",
+                    "{package_manager} update",
+                    "{package_manager} install --no-install-recommends -y {cross_pkgs}",
+                    "{package_manager} autoremove -y",
+                    "{package_manager} autoclean -y",
+                ])
+            elif package_format == "rpm":
+                cross_commands.extend([
+                    "{package_manager} install -y {cross_pkgs}",
+                    "{package_manager} clean all -y",
+                ])
+
+            cross_script = "\nRUN " + (" && \\\n    ".join(cross_commands)) + "\n"
+            sys.stdout.write(cross_script.format(**varmap))
+
+        if "pip_pkgs" in varmap:
+            sys.stdout.write(textwrap.dedent("""
+                RUN pip3 install {pip_pkgs}
+            """).format(**varmap))
+
+
     def _action_dockerfile(self, args):
         mappings = self._projects.get_mappings()
         pip_mappings = self._projects.get_pip_mappings()
@@ -947,6 +1028,7 @@ class Application:
         os_version = facts["os_version"]
         os_full = os_name + os_version
         cross_arch = args.cross_arch
+        inherit = args.inherit
 
         if package_format not in ["deb", "rpm"]:
             raise Exception("Host {} doesn't support Dockerfiles".format(host))
@@ -983,8 +1065,14 @@ class Application:
                     )
                 )
 
-        varmap = self._dockerfile_build_varmap(facts, mappings, pip_mappings, projects, cross_arch)
-        self._dockerfile_format(facts, cross_arch, varmap)
+        needbase = True
+        if inherit is not None:
+            needbase = False
+        varmap = self._dockerfile_build_varmap(facts, mappings, pip_mappings, projects, cross_arch, needbase)
+        if inherit is None:
+            self._dockerfile_base_format(facts, cross_arch, varmap)
+        else:
+            self._dockerfile_child_format(facts, cross_arch, inherit, varmap)
 
     def run(self):
         args = self._parser.parse_args()
-- 
2.24.1




More information about the libvir-list mailing list