| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 | from __future__ import print_functionfrom __future__ import unicode_literalsimport loggingimport reimport signalimport sysfrom inspect import getdocfrom operator import attrgetterfrom docker.errors import APIErrorfrom requests.exceptions import ReadTimeoutfrom .. import __version__from ..config import ConfigurationErrorfrom ..config import parse_environmentfrom ..const import DEFAULT_TIMEOUTfrom ..const import HTTP_TIMEOUTfrom ..const import IS_WINDOWS_PLATFORMfrom ..progress_stream import StreamOutputErrorfrom ..project import NoSuchServicefrom ..service import BuildErrorfrom ..service import ConvergenceStrategyfrom ..service import NeedsBuildErrorfrom .command import friendly_error_messagefrom .command import project_from_optionsfrom .docopt_command import DocoptCommandfrom .docopt_command import NoSuchCommandfrom .errors import UserErrorfrom .formatter import ConsoleWarningFormatterfrom .formatter import Formatterfrom .log_printer import LogPrinterfrom .utils import get_version_infofrom .utils import yesnoif not IS_WINDOWS_PLATFORM:    import dockerptylog = logging.getLogger(__name__)console_handler = logging.StreamHandler(sys.stderr)INSECURE_SSL_WARNING = """--allow-insecure-ssl is deprecated and has no effect.It will be removed in a future version of Compose."""def main():    setup_logging()    try:        command = TopLevelCommand()        command.sys_dispatch()    except KeyboardInterrupt:        log.error("\nAborting.")        sys.exit(1)    except (UserError, NoSuchService, ConfigurationError) as e:        log.error(e.msg)        sys.exit(1)    except NoSuchCommand as e:        commands = "\n".join(parse_doc_section("commands:", getdoc(e.supercommand)))        log.error("No such command: %s\n\n%s", e.command, commands)        sys.exit(1)    except APIError as e:        log.error(e.explanation)        sys.exit(1)    except BuildError as e:        log.error("Service '%s' failed to build: %s" % (e.service.name, e.reason))        sys.exit(1)    except StreamOutputError as e:        log.error(e)        sys.exit(1)    except NeedsBuildError as e:        log.error("Service '%s' needs to be built, but --no-build was passed." % e.service.name)        sys.exit(1)    except ReadTimeout as e:        log.error(            "An HTTP request took too long to complete. Retry with --verbose to obtain debug information.\n"            "If you encounter this issue regularly because of slow network conditions, consider setting "            "COMPOSE_HTTP_TIMEOUT to a higher value (current value: %s)." % HTTP_TIMEOUT        )        sys.exit(1)def setup_logging():    root_logger = logging.getLogger()    root_logger.addHandler(console_handler)    root_logger.setLevel(logging.DEBUG)    # Disable requests logging    logging.getLogger("requests").propagate = Falsedef setup_console_handler(handler, verbose):    if handler.stream.isatty():        format_class = ConsoleWarningFormatter    else:        format_class = logging.Formatter    if verbose:        handler.setFormatter(format_class('%(name)s.%(funcName)s: %(message)s'))        handler.setLevel(logging.DEBUG)    else:        handler.setFormatter(format_class())        handler.setLevel(logging.INFO)# stolen from docopt masterdef parse_doc_section(name, source):    pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)',                         re.IGNORECASE | re.MULTILINE)    return [s.strip() for s in pattern.findall(source)]class TopLevelCommand(DocoptCommand):    """Define and run multi-container applications with Docker.    Usage:      docker-compose [-f=<arg>...] [options] [COMMAND] [ARGS...]      docker-compose -h|--help    Options:      -f, --file FILE           Specify an alternate compose file (default: docker-compose.yml)      -p, --project-name NAME   Specify an alternate project name (default: directory name)      --x-networking            (EXPERIMENTAL) Use new Docker networking functionality.                                Requires Docker 1.9 or later.      --x-network-driver DRIVER (EXPERIMENTAL) Specify a network driver (default: "bridge").                                Requires Docker 1.9 or later.      --verbose                 Show more output      -v, --version             Print version and exit    Commands:      build              Build or rebuild services      help               Get help on a command      kill               Kill containers      logs               View output from containers      pause              Pause services      port               Print the public port for a port binding      ps                 List containers      pull               Pulls service images      restart            Restart services      rm                 Remove stopped containers      run                Run a one-off command      scale              Set number of containers for a service      start              Start services      stop               Stop services      unpause            Unpause services      up                 Create and start containers      version            Show the Docker-Compose version information    """    base_dir = '.'    def docopt_options(self):        options = super(TopLevelCommand, self).docopt_options()        options['version'] = get_version_info('compose')        return options    def perform_command(self, options, handler, command_options):        setup_console_handler(console_handler, options.get('--verbose'))        if options['COMMAND'] in ('help', 'version'):            # Skip looking up the compose file.            handler(None, command_options)            return        project = project_from_options(self.base_dir, options)        with friendly_error_message():            handler(project, command_options)    def build(self, project, options):        """        Build or rebuild services.        Services are built once and then tagged as `project_service`,        e.g. `composetest_db`. If you change a service's `Dockerfile` or the        contents of its build directory, you can run `docker-compose build` to rebuild it.        Usage: build [options] [SERVICE...]        Options:            --force-rm  Always remove intermediate containers.            --no-cache  Do not use cache when building the image.            --pull      Always attempt to pull a newer version of the image.        """        project.build(            service_names=options['SERVICE'],            no_cache=bool(options.get('--no-cache', False)),            pull=bool(options.get('--pull', False)),            force_rm=bool(options.get('--force-rm', False)))    def help(self, project, options):        """        Get help on a command.        Usage: help COMMAND        """        handler = self.get_handler(options['COMMAND'])        raise SystemExit(getdoc(handler))    def kill(self, project, options):        """        Force stop service containers.        Usage: kill [options] [SERVICE...]        Options:            -s SIGNAL         SIGNAL to send to the container.                              Default signal is SIGKILL.        """        signal = options.get('-s', 'SIGKILL')        project.kill(service_names=options['SERVICE'], signal=signal)    def logs(self, project, options):        """        View output from containers.        Usage: logs [options] [SERVICE...]        Options:            --no-color  Produce monochrome output.        """        containers = project.containers(service_names=options['SERVICE'], stopped=True)        monochrome = options['--no-color']        print("Attaching to", list_containers(containers))        LogPrinter(containers, monochrome=monochrome).run()    def pause(self, project, options):        """        Pause services.        Usage: pause [SERVICE...]        """        project.pause(service_names=options['SERVICE'])    def port(self, project, options):        """        Print the public port for a port binding.        Usage: port [options] SERVICE PRIVATE_PORT        Options:            --protocol=proto  tcp or udp [default: tcp]            --index=index     index of the container if there are multiple                              instances of a service [default: 1]        """        index = int(options.get('--index'))        service = project.get_service(options['SERVICE'])        try:            container = service.get_container(number=index)        except ValueError as e:            raise UserError(str(e))        print(container.get_local_port(            options['PRIVATE_PORT'],            protocol=options.get('--protocol') or 'tcp') or '')    def ps(self, project, options):        """        List containers.        Usage: ps [options] [SERVICE...]        Options:            -q    Only display IDs        """        containers = sorted(            project.containers(service_names=options['SERVICE'], stopped=True) +            project.containers(service_names=options['SERVICE'], one_off=True),            key=attrgetter('name'))        if options['-q']:            for container in containers:                print(container.id)        else:            headers = [                'Name',                'Command',                'State',                'Ports',            ]            rows = []            for container in containers:                command = container.human_readable_command                if len(command) > 30:                    command = '%s ...' % command[:26]                rows.append([                    container.name,                    command,                    container.human_readable_state,                    container.human_readable_ports,                ])            print(Formatter().table(headers, rows))    def pull(self, project, options):        """        Pulls images for services.        Usage: pull [options] [SERVICE...]        Options:            --ignore-pull-failures  Pull what it can and ignores images with pull failures.            --allow-insecure-ssl    Deprecated - no effect.        """        if options['--allow-insecure-ssl']:            log.warn(INSECURE_SSL_WARNING)        project.pull(            service_names=options['SERVICE'],            ignore_pull_failures=options.get('--ignore-pull-failures')        )    def rm(self, project, options):        """        Remove stopped service containers.        Usage: rm [options] [SERVICE...]        Options:            -f, --force   Don't ask to confirm removal            -v            Remove volumes associated with containers        """        all_containers = project.containers(service_names=options['SERVICE'], stopped=True)        stopped_containers = [c for c in all_containers if not c.is_running]        if len(stopped_containers) > 0:            print("Going to remove", list_containers(stopped_containers))            if options.get('--force') \                    or yesno("Are you sure? [yN] ", default=False):                project.remove_stopped(                    service_names=options['SERVICE'],                    v=options.get('-v', False)                )        else:            print("No stopped containers")    def run(self, project, options):        """        Run a one-off command on a service.        For example:            $ docker-compose run web python manage.py shell        By default, linked services will be started, unless they are already        running. If you do not want to start linked services, use        `docker-compose run --no-deps SERVICE COMMAND [ARGS...]`.        Usage: run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]        Options:            --allow-insecure-ssl  Deprecated - no effect.            -d                    Detached mode: Run container in the background, print                                  new container name.            --name NAME           Assign a name to the container            --entrypoint CMD      Override the entrypoint of the image.            -e KEY=VAL            Set an environment variable (can be used multiple times)            -u, --user=""         Run as specified username or uid            --no-deps             Don't start linked services.            --rm                  Remove container after run. Ignored in detached mode.            -p, --publish=[]      Publish a container's port(s) to the host            --service-ports       Run command with the service's ports enabled and mapped                                  to the host.            -T                    Disable pseudo-tty allocation. By default `docker-compose run`                                  allocates a TTY.        """        service = project.get_service(options['SERVICE'])        detach = options['-d']        if IS_WINDOWS_PLATFORM and not detach:            raise UserError(                "Interactive mode is not yet supported on Windows.\n"                "Please pass the -d flag when using `docker-compose run`."            )        if options['--allow-insecure-ssl']:            log.warn(INSECURE_SSL_WARNING)        if options['COMMAND']:            command = [options['COMMAND']] + options['ARGS']        else:            command = service.options.get('command')        container_options = {            'command': command,            'tty': not (detach or options['-T'] or not sys.stdin.isatty()),            'stdin_open': not detach,            'detach': detach,        }        if options['-e']:            container_options['environment'] = parse_environment(options['-e'])        if options['--entrypoint']:            container_options['entrypoint'] = options.get('--entrypoint')        if options['--rm']:            container_options['restart'] = None        if options['--user']:            container_options['user'] = options.get('--user')        if not options['--service-ports']:            container_options['ports'] = []        if options['--publish']:            container_options['ports'] = options.get('--publish')        if options['--publish'] and options['--service-ports']:            raise UserError(                'Service port mapping and manual port mapping '                'can not be used togather'            )        if options['--name']:            container_options['name'] = options['--name']        run_one_off_container(container_options, project, service, options)    def scale(self, project, options):        """        Set number of containers to run for a service.        Numbers are specified in the form `service=num` as arguments.        For example:            $ docker-compose scale web=2 worker=3        Usage: scale [options] [SERVICE=NUM...]        Options:          -t, --timeout TIMEOUT      Specify a shutdown timeout in seconds.                                     (default: 10)        """        timeout = int(options.get('--timeout') or DEFAULT_TIMEOUT)        for s in options['SERVICE=NUM']:            if '=' not in s:                raise UserError('Arguments to scale should be in the form service=num')            service_name, num = s.split('=', 1)            try:                num = int(num)            except ValueError:                raise UserError('Number of containers for service "%s" is not a '                                'number' % service_name)            project.get_service(service_name).scale(num, timeout=timeout)    def start(self, project, options):        """        Start existing containers.        Usage: start [SERVICE...]        """        project.start(service_names=options['SERVICE'])    def stop(self, project, options):        """        Stop running containers without removing them.        They can be started again with `docker-compose start`.        Usage: stop [options] [SERVICE...]        Options:          -t, --timeout TIMEOUT      Specify a shutdown timeout in seconds.                                     (default: 10)        """        timeout = int(options.get('--timeout') or DEFAULT_TIMEOUT)        project.stop(service_names=options['SERVICE'], timeout=timeout)    def restart(self, project, options):        """        Restart running containers.        Usage: restart [options] [SERVICE...]        Options:          -t, --timeout TIMEOUT      Specify a shutdown timeout in seconds.                                     (default: 10)        """        timeout = int(options.get('--timeout') or DEFAULT_TIMEOUT)        project.restart(service_names=options['SERVICE'], timeout=timeout)    def unpause(self, project, options):        """        Unpause services.        Usage: unpause [SERVICE...]        """        project.unpause(service_names=options['SERVICE'])    def up(self, project, options):        """        Builds, (re)creates, starts, and attaches to containers for a service.        Unless they are already running, this command also starts any linked services.        The `docker-compose up` command aggregates the output of each container. When        the command exits, all containers are stopped. Running `docker-compose up -d`        starts the containers in the background and leaves them running.        If there are existing containers for a service, and the service's configuration        or image was changed after the container's creation, `docker-compose up` picks        up the changes by stopping and recreating the containers (preserving mounted        volumes). To prevent Compose from picking up changes, use the `--no-recreate`        flag.        If you want to force Compose to stop and recreate all containers, use the        `--force-recreate` flag.        Usage: up [options] [SERVICE...]        Options:            --allow-insecure-ssl   Deprecated - no effect.            -d                     Detached mode: Run containers in the background,                                   print new container names.            --no-color             Produce monochrome output.            --no-deps              Don't start linked services.            --force-recreate       Recreate containers even if their configuration and                                   image haven't changed. Incompatible with --no-recreate.            --no-recreate          If containers already exist, don't recreate them.                                   Incompatible with --force-recreate.            --no-build             Don't build an image, even if it's missing            -t, --timeout TIMEOUT  Use this timeout in seconds for container shutdown                                   when attached or when containers are already                                   running. (default: 10)        """        if options['--allow-insecure-ssl']:            log.warn(INSECURE_SSL_WARNING)        monochrome = options['--no-color']        start_deps = not options['--no-deps']        service_names = options['SERVICE']        timeout = int(options.get('--timeout') or DEFAULT_TIMEOUT)        detached = options.get('-d')        to_attach = project.up(            service_names=service_names,            start_deps=start_deps,            strategy=convergence_strategy_from_opts(options),            do_build=not options['--no-build'],            timeout=timeout,            detached=detached        )        if not detached:            log_printer = build_log_printer(to_attach, service_names, monochrome)            attach_to_logs(project, log_printer, service_names, timeout)    def version(self, project, options):        """        Show version informations        Usage: version [--short]        Options:            --short     Shows only Compose's version number.        """        if options['--short']:            print(__version__)        else:            print(get_version_info('full'))def convergence_strategy_from_opts(options):    no_recreate = options['--no-recreate']    force_recreate = options['--force-recreate']    if force_recreate and no_recreate:        raise UserError("--force-recreate and --no-recreate cannot be combined.")    if force_recreate:        return ConvergenceStrategy.always    if no_recreate:        return ConvergenceStrategy.never    return ConvergenceStrategy.changeddef run_one_off_container(container_options, project, service, options):    if not options['--no-deps']:        deps = service.get_linked_service_names()        if deps:            project.up(                service_names=deps,                start_deps=True,                strategy=ConvergenceStrategy.never)    if project.use_networking:        project.ensure_network_exists()    container = service.create_container(        quiet=True,        one_off=True,        **container_options)    if options['-d']:        container.start()        print(container.name)        return    def remove_container(force=False):        if options['--rm']:            project.client.remove_container(container.id, force=True)    def force_shutdown(signal, frame):        project.client.kill(container.id)        remove_container(force=True)        sys.exit(2)    def shutdown(signal, frame):        set_signal_handler(force_shutdown)        project.client.stop(container.id)        remove_container()        sys.exit(1)    set_signal_handler(shutdown)    dockerpty.start(project.client, container.id, interactive=not options['-T'])    exit_code = container.wait()    remove_container()    sys.exit(exit_code)def build_log_printer(containers, service_names, monochrome):    if service_names:        containers = [            container            for container in containers if container.service in service_names        ]    return LogPrinter(containers, monochrome=monochrome)def attach_to_logs(project, log_printer, service_names, timeout):    def force_shutdown(signal, frame):        project.kill(service_names=service_names)        sys.exit(2)    def shutdown(signal, frame):        set_signal_handler(force_shutdown)        print("Gracefully stopping... (press Ctrl+C again to force)")        project.stop(service_names=service_names, timeout=timeout)    print("Attaching to", list_containers(log_printer.containers))    set_signal_handler(shutdown)    log_printer.run()def set_signal_handler(handler):    signal.signal(signal.SIGINT, handler)    signal.signal(signal.SIGTERM, handler)def list_containers(containers):    return ", ".join(c.name for c in containers)
 |