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@xxxxxxxxxx> --- 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