diff --git a/.gitignore b/.gitignore index 2dc9b698..3addb4ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,4 @@ *~ -.build.server -.build.opensuse-server -.build.ad-server -.build.opensuse-ad-server -.build.client -.build.opensuse-client -.build.toolbox -.build.opensuse-toolbox -.build.nightly.server -.build.nightly.ad-server +.build.* .common .bin diff --git a/examples/custom-server.ini b/examples/custom-server.ini new file mode 100644 index 00000000..de4ff0b6 --- /dev/null +++ b/examples/custom-server.ini @@ -0,0 +1,19 @@ +# Example config file for passing to hack/build-image `-C` option. +# Use this as a template to create customized images using work +# in progress branches of sambacc or custom builds of samba/ceph/etc +# packages. + +[image] +custom_repos = + http://my.rpmserver.example.org/foo/bar/samba/config.repo + http://my.rpmserver.exampel.org/foo/bar/ceph/config.repo +package_selection = forcedevbuilds +ceph_from_custom = 1 +extra_arguments = --cache-ttl=1h + +[sambacc] +#base_image = sambacc:ci +sambacc_repo = https://github.com/samba-in-kubernetes/sambacc +sambacc_ver = my-branch +distname = latest + diff --git a/hack/build-image b/hack/build-image index 23881840..4e23ddec 100755 --- a/hack/build-image +++ b/hack/build-image @@ -36,6 +36,8 @@ Usage: """ import argparse +import configparser +import contextlib import logging import os import pathlib @@ -106,7 +108,7 @@ PACKAGES_FROM = { DEFAULT: "", NIGHTLY: "samba-nightly", DEVBUILDS: "devbuilds", - CUSTOM: "custom-repos" + CUSTOM: "custom-repos", } # SOURCE_DIRS - image source paths @@ -203,72 +205,79 @@ def container_engine(cli): return _DISCOVERED_CONTAINER_ENGINES[0] -def container_build(cli, target): - """Construct and execute a command to build the target container image.""" +def _buildx_build_tasks(cli, target, containerfile=""): eng = container_engine(cli) tasks = [] + args = [eng, "buildx"] + + # Docker's default builder only supports the host architecture. + # Therefore, we need to create a new builder to support other + # architectures, and we must ensure we start with a fresh builder + # that does not contain any images from previous builds. + tasks.append({"cmd": args + ["rm", target.flat_name()], "check": False}) + tasks.append( + { + "cmd": args + ["create", f"--name={target.flat_name()}"], + "check": True, + } + ) - # For docker cross-builds we need to use buildx - if "docker" in eng and target.arch != host_arch(): - args = [eng, "buildx"] - - # Docker's default builder only supports the host architecture. - # Therefore, we need to create a new builder to support other - # architectures, and we must ensure we start with a fresh builder - # that does not contain any images from previous builds. - tasks.append( - lambda: run(cli, args + ["rm", target.flat_name()], check=False) - ) - tasks.append( - lambda: run( - cli, - args + ["create", f"--name={target.flat_name()}"], - check=True, - ) - ) + tasks.append( + { + "cmd": args + + [ + "build", + f"--builder={target.flat_name()}", + f"--platform=linux/{target.arch}", + "--load", + ] + + create_common_container_engine_args(cli, target, containerfile), + "check": True, + } + ) - tasks.append( - lambda: run( - cli, - args - + [ - "build", - f"--builder={target.flat_name()}", - f"--platform=linux/{target.arch}", - "--load", - ] - + create_common_container_engine_args(cli, target), - check=True, - ) - ) + tasks.append({"cmd": args + ["rm", target.flat_name()], "check": True}) + return tasks - tasks.append( - lambda: run(cli, args + ["rm", target.flat_name()], check=True) - ) - else: - args = [eng, "build"] - if target.arch != host_arch() or FORCE_ARCH_FLAG: - # We've noticed a few small quirks when using podman with the --arch - # option. The main issue is that building the client image works - # but then the toolbox image fails because it somehow doesn't see - # the image we just built as usable. This doesn't happen when - # --arch is not provided. So if the target arch and the host_arch - # are the same, skip passing the extra argument. - args += [f"--arch={target.arch}"] - - tasks.append( - lambda: run( - cli, - args + create_common_container_engine_args(cli, target), - check=True, - ) - ) - for task in tasks: - task() +def _common_build_tasks(cli, target, containerfile=""): + # podman/common build tasks + eng = container_engine(cli) + tasks = [] + args = [eng, "build"] + if target.arch != host_arch() or FORCE_ARCH_FLAG: + # We've noticed a few small quirks when using podman with the + # --arch option. The main issue is that building the client image + # works but then the toolbox image fails because it somehow doesn't + # see the image we just built as usable. This doesn't happen when + # --arch is not provided. So if the target arch and the host_arch + # are the same, skip passing the extra argument. + args += [f"--arch={target.arch}"] + + tasks.append( + { + "cmd": args + + create_common_container_engine_args(cli, target, containerfile), + "check": True, + } + ) + return tasks + + +def container_build(cli, target): + """Construct and execute a command to build the target container image.""" + eng = container_engine(cli) + # For docker cross-builds we need to use buildx + with edited_containerfile(cli, target) as containerfile: + if "docker" in eng and target.arch != host_arch(): + tasks = _buildx_build_tasks(cli, target, containerfile) + else: + tasks = _common_build_tasks(cli, target, containerfile) + for task in tasks: + run(cli, **task) -def create_common_container_engine_args(cli, target): +def create_common_container_engine_args(cli, target, containerfile=""): args = [] pkgs_from = PACKAGES_FROM[target.pkg_source] if pkgs_from: @@ -282,19 +291,111 @@ def create_common_container_engine_args(cli, target): "ctdb_rados_mutex_skip_reg" ) - if cli.extra_build_arg: - args.extend(cli.extra_build_arg) + args.extend(cli.extra_build_arguments()) for tname in target.all_names(baseless=cli.without_repo_bases): args.append("-t") args.append(tname) args.append("-f") - args.append(target_containerfile(target)) + args.append(containerfile or target_containerfile(target)) args.append(kind_source_dir(target.name)) return [str(a) for a in args] +@contextlib.contextmanager +def edited_containerfile(cli, target): + if "sambacc" not in cli.customizations(): + yield target_containerfile(target) + return + orig_containerfile = target_containerfile(target) + try: + wip = ( + orig_containerfile.parent + / f".{orig_containerfile.name}.{os.getpid()}.tmp" + ) + with open(wip, "w") as fh: + for line in _sambacc_prefix(cli): + fh.write(line) + fh.write("\n") + replacements = {"sambacc": _sambacc_install(cli)} + for line, meta in _scan_containerfile(orig_containerfile): + if meta in replacements: + for line in replacements[meta]: + fh.write(line) + fh.write("\n") + replacements.pop(meta, None) + continue + elif meta: + continue + fh.write(line) + with open(wip) as fh: + print(fh.read()) + yield wip + finally: + wip.unlink(missing_ok=True) + + +def _sambacc_prefix(cli): + cfg = cli.customizations()["sambacc"] + base_image = cfg.get("base_image", "quay.io/samba.org/sambacc:latest") + sambacc_ver = cfg.get("sambacc_ver", "master") + sambacc_repo = cfg.get( + "sambacc_repo", "https://github.com/samba-in-kubernetes/sambacc" + ) + distname = cfg.get("distname", "latest") + distpath = f"/srv/dist/{distname}" + yield "# --- sambacc prefix ---" + yield f"FROM {base_image} AS sccbuilder" + yield f"ARG SAMBACC_VER={sambacc_ver}" + yield f"ARG SAMBACC_REPO={sambacc_repo}" + yield ( + f"RUN SAMBACC_DISTNAME={distname}" + " /usr/local/bin/build.sh ${SAMBACC_VER} ${SAMBACC_REPO}" + ) + yield ( + "RUN dnf install -y /usr/bin/createrepo_c" + f" && createrepo_c {distpath}" + f" && echo -e '[sambacc]\\nbaseurl=file://{distpath}\\nenabled=1\\ngpgcheck=0\\n'" + f" > {distpath}/sambacc.repo" + ) + yield "# --- sambacc prefix ---" + yield "" + + +def _sambacc_install(cli): + cfg = cli.customizations()["sambacc"] + distname = cfg.get("distname", "latest") + distpath = f"/srv/dist/{distname}" + yield "# --- begin modified sambacc install ---" + yield ( + "RUN" + f" --mount=type=bind,from=sccbuilder,source={distpath},destination={distpath}" + f" bash -x /usr/local/bin/install-sambacc.sh {distpath}" + " ${SAMBACC_VERSION_SUFFIX}" + ) + yield "# --- end modified sambacc install ---" + yield "" + + +def _scan_containerfile(path, tags="sambacc"): + tags = tags.split() + active_tag = None + with open(path) as fh: + for line in fh: + for tag in tags: + if f"---begin {tag}---" in line: + active_tag = tag + yield line, active_tag + for tag in tags: + if f"---end {tag}--" in line: + if tag != active_tag: + raise ValueError( + f"bad tag: {tag}, expected {active_tag}" + ) + active_tag = None + + def container_push(cli, push_name): """Construct and execute a command to push a container image.""" args = [container_engine(cli), "push", push_name] @@ -343,9 +444,7 @@ def kind_source_dir(kind): def target_containerfile(target): """Return the path to a containerfile given an image target.""" - return str( - kind_source_dir(target.name) / f"Containerfile.{target.distro}" - ) + return kind_source_dir(target.name) / f"Containerfile.{target.distro}" def host_arch(): @@ -466,16 +565,17 @@ def add_special_tags(img, distro_qualified=True): # that certain images deserve some extra special tags. Mostly this serves # to keep us compatible with older tagging schemes from earlier versions of # the project. + _host_arch = host_arch() if img.distro in [FEDORA, OPENSUSE]: - if img.arch == host_arch() and img.pkg_source == DEFAULT: + if img.arch == _host_arch and img.pkg_source == DEFAULT: img.additional_tags.append((LATEST, QUAL_NONE)) - if img.arch == host_arch() and img.pkg_source == NIGHTLY: + if img.arch == _host_arch and img.pkg_source == NIGHTLY: img.additional_tags.append((NIGHTLY, QUAL_NONE)) if not distro_qualified: return # skip creating "distro qualified" tags - if img.arch == host_arch() and img.pkg_source == "default": + if img.arch == _host_arch and img.pkg_source == DEFAULT: img.additional_tags.append((f"{img.distro}-{LATEST}", QUAL_DISTRO)) - if img.arch == host_arch() and img.pkg_source == "nightly": + if img.arch == _host_arch and img.pkg_source == NIGHTLY: img.additional_tags.append((f"{img.distro}-{NIGHTLY}", QUAL_DISTRO)) @@ -601,6 +701,60 @@ def print_tags(cli, target): print(f"{prefix}{name}") +class CLIContext: + def __init__(self, cli): + self._cli = cli + self._customizations = None + + def __getattr__(self, key): + return getattr(self._cli, key) + + def read_customizations(self): + if not self._cli.customizations: + return + self._customizations = configparser.ConfigParser() + files = self._customizations.read(self._cli.customizations) + if not files: + raise ValueError("no customization files could be read") + + def customizations(self): + if self._customizations is None: + self.read_customizations() + return self._customizations or configparser.ConfigParser() + + def extra_build_arguments(self): + args = [] + # extra args from customizations file + if "image" in self.customizations(): + cimg = self.customizations()["image"] + for build_arg, vtype, name in self._args_custom_vars(): + cvalue = cimg.get(name, None) + if cvalue is None: + continue + if vtype is list: + cvalue = " ".join(cvalue.strip().split()) + elif vtype is bool: + cvalue = str(int(bool(cvalue))) + args.append(f"--build-arg={build_arg}={cvalue}") + if xargs := cimg.get("extra_arguments"): + args.extend(shlex.split(xargs.strip())) + # CLI extra args + if self._cli.extra_build_arg: + args.extend(self._cli.extra_build_arg) + return args + + def _args_custom_vars(self): + return [ + ("INSTALL_PACKAGES_FROM", str, "install_packages_from"), + ("SAMBA_VERSION_SUFFIX", str, "samba_version_suffix"), + ("SAMBACC_VERSION_SUFFIX", str, "sambacc_version_suffix"), + ("SAMBA_SPECIFICS", str, "samba_specifics"), + ("INSTALL_CUSTOM_REPOS", list, "custom_repos"), + ("PACKAGE_SELECTION", str, "package_selection"), + ("CEPH_FROM_CUSTOM", bool, "ceph_from_custom"), + ] + + def main(): parser = argparse.ArgumentParser() parser.add_argument( @@ -720,6 +874,12 @@ def main(): " without the repo base" ), ) + parser.add_argument( + "--customizations", + "-C", + type=pathlib.Path, + help="", + ) parser.add_argument( "--distro-qualified", action=argparse.BooleanOptionalAction, @@ -768,7 +928,7 @@ def main(): " for a given FQIN. Requires FQIN to already exist locally." ), ) - cli = parser.parse_args() + cli = CLIContext(parser.parse_args()) if os.environ.get("BUILD_IMAGE_DEBUG") in ("1", "yes"): cli.log_level = logging.DEBUG diff --git a/images/server/Containerfile.centos b/images/server/Containerfile.centos index 7892bc02..b60ebf42 100644 --- a/images/server/Containerfile.centos +++ b/images/server/Containerfile.centos @@ -5,6 +5,7 @@ ARG SAMBACC_VERSION_SUFFIX="" ARG SAMBA_SPECIFICS=daemon_cli_debug_output,ctdb_leader_admin_command ARG INSTALL_CUSTOM_REPOS= ARG PACKAGE_SELECTION= +ARG CEPH_FROM_CUSTOM=0 MAINTAINER John Mulligan @@ -20,6 +21,7 @@ RUN /usr/local/bin/install-packages.sh \ "--install-packages-from=${INSTALL_PACKAGES_FROM}" \ "--samba-version-suffix=${SAMBA_VERSION_SUFFIX}" \ "--install-custom-repos=${INSTALL_CUSTOM_REPOS}" \ + "--ceph-from-custom=${CEPH_FROM_CUSTOM}" \ "--package-selection=${PACKAGE_SELECTION}" # If you want to install a custom version of sambacc into this image mount @@ -30,9 +32,11 @@ RUN /usr/local/bin/install-packages.sh \ # https://copr.fedorainfracloud.org/coprs/phlogistonjohn/sambacc COPY .common/install-sambacc-common.sh /usr/local/bin/install-sambacc-common.sh COPY install-sambacc.sh /usr/local/bin/install-sambacc.sh +# ---begin sambacc--- RUN /usr/local/bin/install-sambacc.sh \ "/tmp/sambacc-dist-latest" \ "${SAMBACC_VERSION_SUFFIX}" +# ---end sambacc--- VOLUME ["/share"] diff --git a/images/server/install-packages.sh b/images/server/install-packages.sh index 1ca92a8f..e7604b13 100755 --- a/images/server/install-packages.sh +++ b/images/server/install-packages.sh @@ -105,7 +105,8 @@ if [[ "$1" =~ ^--.+$ ]]; then --samba-version-suffix=*) samba_version_suffix="${arg/*=/}" ;; --install-custom-repos=*) install_custom_repos="${arg/*=/}" ;; --package-selection=*) package_selection="${arg/*=/}" ;; - --ceph-from-custom) ceph_from_custom=1 ;; + --ceph-from-custom|--ceph-from-custom=1) ceph_from_custom=1 ;; + --ceph-from-custom=0) ceph_from_custom=0 ;; *) echo "error: unexpected argument: ${arg}" exit 2