Skip to content
Snippets Groups Projects
launch 7.04 KiB
#!/usr/bin/env python3

import argparse
import json
import os
import re
import subprocess
import sys
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',
}


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 dev_from_base_image_tag(base_image_tag):
    split = base_image_tag.split(':', 1)
    if len(split) == 1:
        image, tag = split[0], 'latest'
    else:
        image, tag = split

    # for the dev image, `testing` tags don't bring much. Instead, they
    # could a source of confusion if both `testing` and `latest` images
    # happen to have been pushed with inconsistent versions (especially if
    # `testing` is older than `latest`)
    tag = tag.replace('testing', 'latest')
    return ':'.join((image + '-dev', tag))


def get_volumes_dir(cl_args, arg_parser):
    if cl_args.volumes_dir:
        return cl_args.volumes_dir

    image_tag = cl_args.image_tag
    docker_repo, tag = image_tag.split(':', 1)
    if docker_repo == 'octobus/heptapod':
        subdir = tag
    else:
        arg_parser.error("Please provide an explicit --volumes-dir value "
                         "when using an image not from octobus/heptapod  "
                         "(got %r)" % image_tag)
    return os.path.join(os.environ['HOME'], 'heptapod-volumes', subdir)

DEFAULT_IMAGE_TAG = 'octobus/heptapod:testing'

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 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=81)
    run_group.add_argument('--ssh-port', type=int, default=2022)
    run_group.add_argument('--container-name', default='heptapod')

    img_group = parser.add_argument_group("Docker image options")
    img_group.add_argument('--image-tag',
                           default=DEFAULT_IMAGE_TAG,
                           help="Tag of the image to run ")

    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)

    do_build_dev = do_build_base = False

    image_tag = parsed_args.image_tag
    run_image_tag = image_tag

    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(run_image_tag)
    print("Docker run command: %r" % run_cmd)
    if not simulate:
        subprocess.check_call(run_cmd)
        print()


if __name__ == '__main__':
    main()