Skip to content

API Reference (incomplete)

This part of the project documentation focuses on an information-oriented approach. Use it as a reference for the technical implementation of the calculator project code.

main()

ogscm entry point

Evaluates given recipes and optionally calls ogscm.app.builder and ogscm.app.deployer.

Source code in ogscm/cli.py
def main():  # pragma: no cover
    """ogscm entry point

    Evaluates given recipes and optionally calls ogscm.app.builder
    and ogscm.app.deployer.
    """

    recipe_args_parser = argparse.ArgumentParser(add_help=False)
    parser = argparse.ArgumentParser(add_help=False)
    recipe_args_parser.add_argument("recipe", nargs="+")
    parser.add_argument("recipe", nargs="+")

    # General args
    parser.add_argument(
        "--version",
        action="version",
        version="%(prog)s {version}".format(version=__version__),
    )
    parser.add_argument("--out", type=str, default="_out", help="Output directory")
    parser.add_argument(
        "--file", type=str, default="", help="Overwrite output recipe file name"
    )
    parser.add_argument(
        "--print",
        "-P",
        dest="print",
        action="store_true",
        help="Print the definition to stdout",
    )
    general_g = parser.add_argument_group("General image config")
    general_g.add_argument(
        "--format", type=str, choices=["docker", "singularity"], default="docker"
    )
    general_g.add_argument(
        "--base_image",
        type=str,
        default="ubuntu:22.04",
        help="The base image.",
    )
    general_g.add_argument(
        "--runtime_base_image",
        type=str,
        default="",
        help="The runtime base image.",
    )
    general_g.add_argument(
        "--cpu-target",
        type=str,
        # https://github.com/archspec/archspec-json/blob/master/cpu/microarchitectures.json#L94
        # Enables mmx, sse4_2, implemented by CPUs since around 2010.
        default="ivybridge",
        choices=[a for a in sorted(archspec.cpu.TARGETS)],
        help="The CPU microarchitecture to optimize for (archspec).",
    )
    build_g = parser.add_argument_group("Image build options")
    build_g.add_argument(
        "--build",
        "-B",
        dest="build",
        action="store_true",
        help="Build the images from the definition files",
    )
    build_g.add_argument(
        "--build_args",
        type=str,
        default="",
        help="Arguments to the build command. Have to be "
        "quoted and **must** start with a space. E.g. "
        "--build_args ' --no-cache'",
    )
    build_g.add_argument(
        "--upload",
        "-U",
        dest="upload",
        action="store_true",
        help="Upload Docker image to registry",
    )
    build_g.add_argument(
        "--registry",
        type=str,
        default="registry.opengeosys.org/ogs/ogs",
        help="The docker registry the image is tagged and " "uploaded to.",
    )
    build_g.add_argument(
        "--tag",
        type=str,
        default="",
        help="The full docker image tag. Overwrites --registry.",
    )
    build_g.add_argument(
        "--convert",
        "-C",
        dest="convert",
        action="store_true",
        help="Convert Docker image to Singularity image",
    )
    build_g.add_argument(
        "--sif_file",
        type=str,
        default="",
        help="Overwrite output singularity image file name",
    )
    build_g.add_argument(
        "--convert-enroot",
        "-E",
        dest="convert_enroot",
        action="store_true",
        help="Convert Docker image to enroot image",
    )
    build_g.add_argument(
        "--enroot-bundle",
        dest="enroot_bundle",
        action="store_true",
        help="Convert enroot image to enroot bundle",
    )
    build_g.add_argument(
        "--enroot_file",
        type=str,
        default="",
        help="Overwrite output enroot image file name",
    )
    build_g.add_argument(
        "--force",
        dest="force",
        action="store_true",
        help="Forces overwriting of image files!",
    )
    build_g.add_argument(
        "--runtime-only",
        "-R",
        dest="runtime_only",
        action="store_true",
        help="Generate multi-stage Dockerfiles for small runtime " "images",
    )
    maint_g = parser.add_argument_group("Maintenance")
    maint_g.add_argument(
        "--clean",
        dest="cleanup",
        action="store_true",
        help="Cleans up generated files in default directories.",
    )
    deploy_g = parser.add_argument_group("Image deployment")
    deploy_g.add_argument(
        "--deploy",
        "-D",
        nargs="?",
        const="ALL",
        type=str,
        default="",
        help="Deploys to all configured hosts (in config/deploy_hosts.yml) with no "
        "additional arguments or to the specified host. Implies --build and "
        "--convert arguments.",
    )

    install_g = parser.add_argument_group("Packages to install")
    install_g.add_argument(
        "--pip",
        nargs="*",
        type=str,
        default=[],
        metavar="package",
        help="Install additional Python packages",
    )
    install_g.add_argument(
        "--packages",
        nargs="*",
        type=str,
        default=[],
        metavar="packages",
        help="Install additional OS packages",
    )

    args = parser.parse_known_args()[0]

    images_out_dir = os.path.abspath(f"{args.out}/images")
    os.makedirs(images_out_dir, exist_ok=True)

    hpccm.config.set_cpu_target(args.cpu_target)
    Stage0 = hpccm.Stage()
    Stage0 += raw(docker="# syntax=docker/dockerfile:experimental")

    if args.runtime_only:
        Stage0.name = "build"
    Stage0 += baseimage(image=args.base_image, _as="build", _arch="x86_64")

    Stage0 += comment(
        f"Generated with ogs-container-maker {__version__}", reformat=False
    )
    Stage0 += packages(
        ospackages=["wget", "tar", "curl", "ca-certificates", "make", "unzip"]
    )

    # Prepare runtime stage
    Stage1 = hpccm.Stage()
    if args.runtime_base_image == "":
        Stage1.baseimage(image=args.base_image)
    else:
        Stage1.baseimage(image=args.runtime_base_image)
        if "jupyter/base-notebook" in args.runtime_base_image:
            Stage1 += raw(docker="USER root")

    cwd = os.getcwd()
    img_file = ""
    out_dir = f"{args.out}/{args.format}"
    toolchain = None
    versions = None  # versions.json

    for recipe in recipe_args_parser.parse_known_args()[0].recipe:
        import importlib.resources as pkg_resources

        from ogscm import recipes

        # https://stackoverflow.com/a/1463370/80480
        ldict = {"filename": recipe}
        try:
            recipe_builtin = pkg_resources.read_text(recipes, recipe)
            if "PYTEST_CURRENT_TEST" in os.environ:
                with open(f"ogscm/recipes/{recipe}") as f:
                    exec(
                        compile(f.read(), f"ogscm/recipes/{recipe}", "exec"),
                        locals(),
                        ldict,
                    )
            else:
                exec(compile(recipe_builtin, recipe, "exec"), locals(), ldict)
        except Exception as err:
            error_class = err.__class__.__name__
            detail = err.args[0]
            cl, exc, tb = sys.exc_info()
            line_number = traceback.extract_tb(tb)[-1][1]
            print(f"{error_class} at line {line_number}: {detail}")
            if not os.path.exists(recipe):
                raise Exception(f"{recipe} does not exist!")

            with open(recipe, "r") as reader:
                exec(compile(reader.read(), recipe, "exec"), locals(), ldict)
        if "out_dir" in ldict:
            out_dir = ldict["out_dir"]
        if "toolchain" in ldict:
            toolchain = ldict["toolchain"]
        if "img_file" not in ldict:
            raise Exception(f"img_file variable has to be set in {recipe}!")
        img_file = ldict["img_file"]
        if "versions" in ldict:
            versions = ldict["versions"]

    # Workaround to get the full help message
    help_parser = argparse.ArgumentParser(
        parents=[parser], formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    help_parser.parse_args()

    # Finally parse
    args = parser.parse_args()

    # container_info
    definition_file = "Dockerfile"
    if args.format == "singularity":
        definition_file = "Singularity.def"
    definition_file_path = os.path.join(out_dir, definition_file)
    if img_file[0] == "-":
        img_file = img_file[1:]
    if args.tag != "":
        tag = args.tag
    else:
        tag = f"{args.registry}/{img_file.lower()}:latest"
    # TODO:
    # context_path_size = len(self.ogsdir)
    # "{self.out_dir[context_path_size+1:]}/{self.definition_file}"
    # end container_info

    if args.cleanup:
        shutil.rmtree(out_dir, ignore_errors=True)
        print("Cleaned up!")
        exit(0)

    os.makedirs(out_dir, exist_ok=True)  # For .scif files

    # General args
    if args.packages:
        Stage0 += packages(ospackages=args.packages)
        Stage1 += packages(ospackages=args.packages)

    if args.pip:
        Stage0 += pip(packages=args.pip, pip="pip3")
        Stage1 += pip(packages=args.pip, pip="pip3")

    # Create definition
    hpccm.config.set_container_format(args.format)
    hpccm.config.set_singularity_version("3.5")

    stages_string = str(Stage0)

    if args.runtime_only:
        runtime_exclude = []
        if hasattr(args, "mfront") and not args.mfront:
            runtime_exclude.append("boost")
        Stage1 += Stage0.runtime(exclude=runtime_exclude)
        if (
            hasattr(args, "compiler")
            and args.compiler == "gcc"
            and args.compiler_version is not None
        ):
            Stage1 += packages(apt=["libstdc++6"])
        if "jupyter/base-notebook" in args.runtime_base_image:
            Stage1 += raw(docker="USER ${NB_USER}")
        stages_string += "\n\n" + str(Stage1)

    # ---------------------------- recipe end -----------------------------
    with open(definition_file_path, "w") as f:
        print(stages_string, file=f)
    if args.print:
        print(stages_string)
    else:
        print(f"Created definition {os.path.abspath(definition_file_path)}")

    # Create image
    if args.build:

        b = builder(args, images_out_dir, img_file, definition_file_path, tag, cwd)
        b.build()

        # Deploy image
        if args.deploy:
            deployer(args.deploy, cwd, b.image_file)

builder

Bases: object

Builds a container from a definition.

Source code in ogscm/app/builder.py
class builder(object):
    """Builds a container from a definition."""

    def __init__(
        self, args, images_out_dir, img_file, definition_file_path, tag, cwd, **kwargs
    ):
        self.__format = args.format
        self.__args = args
        self.__images_out_dir = images_out_dir
        self.__img_file = img_file
        self.__definition_file_path = definition_file_path
        self.__tag = tag
        self.__cwd = cwd

    def build(self):
        """Builds a container."""
        if self.__format == "singularity":
            self.build_singularity()
        else:
            self.build_docker()

    def build_singularity(self):
        """Builds a container with Singularity."""
        sif_file = f"{self.__images_out_dir}/{self.__img_file}.sif"
        subprocess.run(
            f"sudo `which singularity` build --force {sif_file} "
            f"{self.__definition_file_path}",
            shell=True,
            check=True,
        )
        subprocess.run(f"sudo chown $USER {sif_file}", shell=True, check=True)
        print(f"Built Singularity image file: {sif_file}")
        # TODO: adapt this
        exit(0)

    def build_docker(self):
        """Builds a container with Docker, optionally converting to Singularity."""
        buildx = False
        if "arm64" in platform.platform():
            buildx = True
        build_cmd = (
            f"DOCKER_BUILDKIT=1 docker{' buildx' if buildx else ''} build"
            f"{' --output type=docker' if buildx else ''} {self.__args.build_args} "
            f"--platform linux/amd64 "
            f"-t {self.__tag} -f {self.__definition_file_path} ."
        )
        print(f"Running: {build_cmd}")
        subprocess.run(build_cmd, shell=True, check=True)
        inspect_out = subprocess.check_output(
            f"docker inspect {self.__tag} | grep Id", shell=True
        ).decode(sys.stdout.encoding)
        image_id = re.search(r"sha256:(\w*)", inspect_out).group(1)
        image_id_short = image_id[0:12]

        if self.__args.upload:
            subprocess.run(f"docker push {self.__tag}", shell=True, check=True)
        image_base_name = f"{self.__images_out_dir}/{self.__img_file}-{image_id_short}"
        if self.__args.sif_file:
            self.image_file = f"{self.__images_out_dir}/{self.__args.sif_file}"
        else:
            self.image_file = f"{image_base_name}.sif"
        if self.__args.convert:
            if (
                not os.path.exists(self.image_file)
                or self.__args.force
                or self.__args.sif_file
            ):
                subprocess.run(
                    f"cd {self.__cwd} && singularity build --force {self.image_file} "
                    f"docker-daemon:{self.__tag}",
                    shell=True,
                    check=True,
                )
                print(f"Built Singularity image file: {self.image_file}")
            else:
                print(f"Already existing Singularity image file: {self.image_file}")

        if self.__args.enroot_file:
            self.image_file = f"{self.__images_out_dir}/{self.__args.enroot_file}"
        else:
            self.image_file = f"{image_base_name}.sqsh"
        if self.__args.convert_enroot and (
            not os.path.exists(self.image_file) or self.__args.force
        ):
            subprocess.run(
                # See https://www.mankier.com/1/mksquashfs for options.
                f"cd {self.__cwd} && rm -f {self.image_file} && "
                f"ENROOT_SQUASH_OPTIONS='-comp xz -b 512K' enroot import "
                f"-o {self.image_file} dockerd://{self.__tag}",
                shell=True,
                check=True,
            )
            print(f"Wrote image file {self.image_file}")

        bundle_file = f"{self.image_file[:-5]}.run"
        if self.__args.enroot_bundle and (
            not os.path.exists(bundle_file) or self.__args.force
        ):
            subprocess.run(
                f"cd {self.__cwd} && rm -f {bundle_file} && enroot bundle "
                f"-o {bundle_file} {self.image_file}",
                shell=True,
                check=True,
            )
            print(f"Wrote bundle file {bundle_file}")

build()

Builds a container.

Source code in ogscm/app/builder.py
def build(self):
    """Builds a container."""
    if self.__format == "singularity":
        self.build_singularity()
    else:
        self.build_docker()

build_docker()

Builds a container with Docker, optionally converting to Singularity.

Source code in ogscm/app/builder.py
def build_docker(self):
    """Builds a container with Docker, optionally converting to Singularity."""
    buildx = False
    if "arm64" in platform.platform():
        buildx = True
    build_cmd = (
        f"DOCKER_BUILDKIT=1 docker{' buildx' if buildx else ''} build"
        f"{' --output type=docker' if buildx else ''} {self.__args.build_args} "
        f"--platform linux/amd64 "
        f"-t {self.__tag} -f {self.__definition_file_path} ."
    )
    print(f"Running: {build_cmd}")
    subprocess.run(build_cmd, shell=True, check=True)
    inspect_out = subprocess.check_output(
        f"docker inspect {self.__tag} | grep Id", shell=True
    ).decode(sys.stdout.encoding)
    image_id = re.search(r"sha256:(\w*)", inspect_out).group(1)
    image_id_short = image_id[0:12]

    if self.__args.upload:
        subprocess.run(f"docker push {self.__tag}", shell=True, check=True)
    image_base_name = f"{self.__images_out_dir}/{self.__img_file}-{image_id_short}"
    if self.__args.sif_file:
        self.image_file = f"{self.__images_out_dir}/{self.__args.sif_file}"
    else:
        self.image_file = f"{image_base_name}.sif"
    if self.__args.convert:
        if (
            not os.path.exists(self.image_file)
            or self.__args.force
            or self.__args.sif_file
        ):
            subprocess.run(
                f"cd {self.__cwd} && singularity build --force {self.image_file} "
                f"docker-daemon:{self.__tag}",
                shell=True,
                check=True,
            )
            print(f"Built Singularity image file: {self.image_file}")
        else:
            print(f"Already existing Singularity image file: {self.image_file}")

    if self.__args.enroot_file:
        self.image_file = f"{self.__images_out_dir}/{self.__args.enroot_file}"
    else:
        self.image_file = f"{image_base_name}.sqsh"
    if self.__args.convert_enroot and (
        not os.path.exists(self.image_file) or self.__args.force
    ):
        subprocess.run(
            # See https://www.mankier.com/1/mksquashfs for options.
            f"cd {self.__cwd} && rm -f {self.image_file} && "
            f"ENROOT_SQUASH_OPTIONS='-comp xz -b 512K' enroot import "
            f"-o {self.image_file} dockerd://{self.__tag}",
            shell=True,
            check=True,
        )
        print(f"Wrote image file {self.image_file}")

    bundle_file = f"{self.image_file[:-5]}.run"
    if self.__args.enroot_bundle and (
        not os.path.exists(bundle_file) or self.__args.force
    ):
        subprocess.run(
            f"cd {self.__cwd} && rm -f {bundle_file} && enroot bundle "
            f"-o {bundle_file} {self.image_file}",
            shell=True,
            check=True,
        )
        print(f"Wrote bundle file {bundle_file}")

build_singularity()

Builds a container with Singularity.

Source code in ogscm/app/builder.py
def build_singularity(self):
    """Builds a container with Singularity."""
    sif_file = f"{self.__images_out_dir}/{self.__img_file}.sif"
    subprocess.run(
        f"sudo `which singularity` build --force {sif_file} "
        f"{self.__definition_file_path}",
        shell=True,
        check=True,
    )
    subprocess.run(f"sudo chown $USER {sif_file}", shell=True, check=True)
    print(f"Built Singularity image file: {sif_file}")
    # TODO: adapt this
    exit(0)