Skip to content
Snippets Groups Projects
launch 7.2 KiB
Newer Older
#!/usr/bin/env python3

import argparse
import os
import re
import subprocess
import tarfile

HOME = os.environ['HOME']
# ! Docker context directory for the *heptapod-dev* image!
DOCKER_CONTEXT_DIR = os.path.realpath(os.path.dirname(__file__))
OMNIBUS_DIR = os.path.dirname(DOCKER_CONTEXT_DIR)
DOCKER_BASE_CONTEXT_DIR = os.path.join(OMNIBUS_DIR, 'heptapod')
SOURCES_DIR = os.path.join(OMNIBUS_DIR, 'repos')
PREFETCH_SOURCES = {  # name in heptapod_revisions -> repo slug
    'workhorse': 'heptapod-workhorse',
    'shell': 'heptapod-shell',
}
HEPTAPOD_REGISTRY_REPO = 'registry.heptapod.net/heptapod/omnibus-heptapod'
DOCKER_REGISTRY_REPO = 'octobus/heptapod'
DEFAULT_IMAGE_REPO = DOCKER_REGISTRY_REPO
DEFAULT_IMAGE_TAG = 'testing'


def read_docker_file_from_field(path, image_tag_rx='.*?'):
    """Read the FROM of Dockerfile at given path."""
    rx = re.compile(r'^FROM\s*(' + image_tag_rx + r')\s*$')
    with open(path) as dockerfile:
        for line in dockerfile.readlines():
            line = line.strip().split('#', 1)[0]
            match = rx.match(line)
            if match is not None:
                return match.group(1)
    return LookupError("Cant find FROM statement in Dockerfile %r" % path)


def get_volumes_dir(cl_args, arg_parser):
    if cl_args.volumes_dir:
        return cl_args.volumes_dir
    subdir = cl_args.image_tag
    docker_repo = cl_args.image_repo
    if docker_repo not in (DOCKER_REGISTRY_REPO, HEPTAPOD_REGISTRY_REPO):
        arg_parser.error("Please provide an explicit --volumes-dir value "
                         "when using an image not from standard image "
                         "repositories (got %r)" % docker_repo)
    return os.path.join(os.environ['HOME'], 'heptapod-volumes', subdir)
Georges Racinet's avatar
Georges Racinet committed

def expand_join(*segments):
    return os.path.expanduser(os.path.join(*segments))


def print_header(header, underline_char='-'):
    print(header)
    print(underline_char * len(header))
    print()


def print_conf_section(header, elements):
    print_header(header)
    fmt = '%-{}s%s'.format(2 + max(len(le[0]) for le in elements))
    for line_elts in elements:
        print(fmt % (line_elts[0] + ':', line_elts[1]))
    print()


def inspect_archive(tarball):
    """Return the contents of archival metadata as a dict."""
    with tarfile.open(tarball) as tar:
        archival = tar.extractfile('.hg_archival.txt')
        res = dict([token.strip() for token in line.decode().split(':', 1)]
                   for line in archival.readlines())
        archival.close()
        return res


def ensure_volumes(bind_mounts):
    for vol, _ in bind_mounts:
        if not os.path.exists(vol):
            os.makedirs(vol, exist_ok=True)


def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description=__doc__)

    parser.add_argument('-s', '--simulate', action='store_true',
                        help="Output configuration instead of "
                        "executing commands")
    vol_group = parser.add_argument_group("Docker volumes options")
    vol_group.add_argument('--volumes-dir',
                           help="Base directory for the three standard "
                           "GitLab Docker volumes (default value is the "
                           "pure tag part of --image-tag, typically 'testing' "
                           "or 'x-y-testing'")
    vol_group.add_argument('--volume-conf', default="etc",
                           help="Name of the volumes subdirectory for "
                           "configuration")
    vol_group.add_argument('--volume-data', default="var",
                           help="Name of the volumes subdirectory for data")
    vol_group.add_argument('--volume-logs', default="log",
                           help="Name of the volumes subdirectory for logs")
    vol_group.add_argument('--volume-src',
                           default=os.path.dirname(OMNIBUS_DIR),
                           help="Additional directory bind-mounted to "
                           "VOLUME_SRC_TARGET, defaulting to the parent "
                           "directory of the present omnibus working copy. "
                           "Assuming your working copies of heptapod, hg-git, "
                           "etc. are in this VOLUMES_SRC, this makes it easy "
                           "to update the code running in the container "
                           "from them."
                           )
    vol_group.add_argument('--volume-src-target', default='/home/heptapod',
                           help="Where to mount VOLUME_SRC inside the "
                           "container.")

    run_group = parser.add_argument_group("Other Docker run options")
    run_group.add_argument('--http-port', type=int, default=8081)
    run_group.add_argument('--ssh-port', type=int, default=2022)
    run_group.add_argument('--container-name', default='heptapod')
    run_group.add_argument('--podman', action="store_true")

    img_group = parser.add_argument_group("Docker image options")
    img_group.add_argument('--image-tag',
                           default=DEFAULT_IMAGE_TAG,
                           help="Tag (in the strict sense) of "
                           "the image to run")
    img_group.add_argument('--image-repo', '--image-repository',
                           default=DEFAULT_IMAGE_REPO,
                           help="Image repository")

    parsed_args = parser.parse_args()
    simulate = parsed_args.simulate

    volumes_dir = get_volumes_dir(parsed_args, parser)
    etc_vol = expand_join(volumes_dir, parsed_args.volume_conf)
    var_vol = expand_join(volumes_dir, parsed_args.volume_data)
    log_vol = expand_join(volumes_dir, parsed_args.volume_logs)
    src_vol = os.path.expanduser(parsed_args.volume_src)

    image_tag = parsed_args.image_tag
    run_image_tag = image_tag
    podman = bool(parsed_args.podman)
    docker = 'podman' if podman else 'docker'
    if simulate:
        print_header("Simulation summary", underline_char='=')

        print_conf_section("Image",
                           (("Full tag", image_tag),
                            ))
        print_conf_section("Volume bind mounts",
                           (('Configuration', etc_vol),
                            ('Data', var_vol),
                            ('Logs', log_vol),
                            ('Sources', src_vol),
                            ))
        print_header("Commands")

    run_cmd = [docker, 'run', '--detach',
               '--hostname', 'heptapod',
               '--publish', '%d:80' % parsed_args.http_port,
               '--publish', '%d:22' % parsed_args.ssh_port,
               '--name', parsed_args.container_name,
               ]

    bind_mounts = ((var_vol, '/var/opt/gitlab'),
                   (log_vol, '/var/log/gitlab'),
                   (etc_vol, '/etc/gitlab'),
                   (src_vol, parsed_args.volume_src_target),
                   )
    for bm in bind_mounts:
        run_cmd.extend(('--volume', '%s:%s' % bm))
    run_cmd.append(f'{parsed_args.image_repo}:{run_image_tag}')
    print("%s run command: %r" % (docker.capitalize(), run_cmd))
        if podman:
            ensure_volumes(bind_mounts)
        subprocess.check_call(run_cmd)
        print()


if __name__ == '__main__':
    main()