| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 | from __future__ import print_functionfrom __future__ import unicode_literalsfrom inspect import getdocfrom operator import attrgetterimport loggingimport reimport signalimport sysfrom docker.errors import APIErrorimport dockerptyfrom .. import __version__from .. import legacyfrom ..const import DEFAULT_TIMEOUTfrom ..project import NoSuchService, ConfigurationErrorfrom ..service import BuildError, NeedsBuildErrorfrom ..config import parse_environmentfrom .command import Commandfrom .docopt_command import NoSuchCommandfrom .errors import UserErrorfrom .formatter import Formatterfrom .log_printer import LogPrinterfrom .utils import yesno, get_version_infolog = logging.getLogger(__name__)def main():    setup_logging()    try:        command = TopLevelCommand()        command.sys_dispatch()    except KeyboardInterrupt:        log.error("\nAborting.")        sys.exit(1)    except (UserError, NoSuchService, ConfigurationError, legacy.LegacyContainersError) as e:        log.error(e.msg)        sys.exit(1)    except NoSuchCommand as e:        log.error("No such command: %s", e.command)        log.error("")        log.error("\n".join(parse_doc_section("commands:", getdoc(e.supercommand))))        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 NeedsBuildError as e:        log.error("Service '%s' needs to be built, but --no-build was passed." % e.service.name)        sys.exit(1)def setup_logging():    console_handler = logging.StreamHandler(sys.stderr)    console_handler.setFormatter(logging.Formatter())    console_handler.setLevel(logging.INFO)    root_logger = logging.getLogger()    root_logger.addHandler(console_handler)    root_logger.setLevel(logging.DEBUG)    # Disable requests logging    logging.getLogger("requests").propagate = False# 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(Command):    """Define and run multi-container applications with Docker.    Usage:      docker-compose [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)      --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      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      up                 Create and start containers      migrate-to-labels  Recreate containers to add labels      version            Show the Docker-Compose version information    """    def docopt_options(self):        options = super(TopLevelCommand, self).docopt_options()        options['version'] = get_version_info('compose')        return 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:            --no-cache  Do not use cache when building the image.        """        no_cache = bool(options.get('--no-cache', False))        project.build(service_names=options['SERVICE'], no_cache=no_cache)    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, attach_params={'logs': True}, monochrome=monochrome).run()    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:            --allow-insecure-ssl    Allow insecure connections to the docker                                    registry        """        insecure_registry = options['--allow-insecure-ssl']        project.pull(            service_names=options['SERVICE'],            insecure_registry=insecure_registry        )    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] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]        Options:            --allow-insecure-ssl  Allow insecure connections to the docker                                  registry            -d                    Detached mode: Run container in the background, print                                  new container name.            --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.            --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'])        insecure_registry = options['--allow-insecure-ssl']        if not options['--no-deps']:            deps = service.get_linked_names()            if len(deps) > 0:                project.up(                    service_names=deps,                    start_deps=True,                    allow_recreate=False,                    insecure_registry=insecure_registry,                )        tty = True        if options['-d'] or options['-T'] or not sys.stdin.isatty():            tty = False        if options['COMMAND']:            command = [options['COMMAND']] + options['ARGS']        else:            command = service.options.get('command')        container_options = {            'command': command,            'tty': tty,            'stdin_open': not options['-d'],            'detach': options['-d'],        }        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'] = []        container = service.create_container(            quiet=True,            one_off=True,            insecure_registry=insecure_registry,            **container_options        )        if options['-d']:            service.start_container(container)            print(container.name)        else:            dockerpty.start(project.client, container.id, interactive=not options['-T'])            exit_code = container.wait()            if options['--rm']:                project.client.remove_container(container.id)            sys.exit(exit_code)    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 [SERVICE=NUM...]        """        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)    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 = float(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 = float(options.get('--timeout') or DEFAULT_TIMEOUT)        project.restart(service_names=options['SERVICE'], timeout=timeout)    def up(self, project, options):        """        Build, (re)create, start and attach to containers for a service.        By default, `docker-compose up` will aggregate the output of each container, and        when it exits, all containers will be stopped. If you run `docker-compose up -d`,        it'll start the containers in the background and leave them running.        If there are existing containers for a service, `docker-compose up` will stop        and recreate them (preserving mounted volumes with volumes-from),        so that changes in `docker-compose.yml` are picked up. If you do not want existing        containers to be recreated, `docker-compose up --no-recreate` will re-use existing        containers.        Usage: up [options] [SERVICE...]        Options:            --allow-insecure-ssl   Allow insecure connections to the docker                                   registry            -d                     Detached mode: Run containers in the background,                                   print new container names.            --no-color             Produce monochrome output.            --no-deps              Don't start linked services.            --x-smart-recreate     Only recreate containers whose configuration or                                   image needs to be updated. (EXPERIMENTAL)            --no-recreate          If containers already exist, don't recreate them.            --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)        """        insecure_registry = options['--allow-insecure-ssl']        detached = options['-d']        monochrome = options['--no-color']        start_deps = not options['--no-deps']        allow_recreate = not options['--no-recreate']        smart_recreate = options['--x-smart-recreate']        service_names = options['SERVICE']        timeout = float(options.get('--timeout') or DEFAULT_TIMEOUT)        to_attach = project.up(            service_names=service_names,            start_deps=start_deps,            allow_recreate=allow_recreate,            smart_recreate=smart_recreate,            insecure_registry=insecure_registry,            do_build=not options['--no-build'],            timeout=timeout        )        if not detached:            print("Attaching to", list_containers(to_attach))            log_printer = LogPrinter(to_attach, attach_params={"logs": True}, monochrome=monochrome)            try:                log_printer.run()            finally:                def handler(signal, frame):                    project.kill(service_names=service_names)                    sys.exit(0)                signal.signal(signal.SIGINT, handler)                print("Gracefully stopping... (press Ctrl+C again to force)")                project.stop(service_names=service_names, timeout=timeout)    def migrate_to_labels(self, project, _options):        """        Recreate containers to add labels        If you're coming from Compose 1.2 or earlier, you'll need to remove or        migrate your existing containers after upgrading Compose. This is        because, as of version 1.3, Compose uses Docker labels to keep track        of containers, and so they need to be recreated with labels added.        If Compose detects containers that were created without labels, it        will refuse to run so that you don't end up with two sets of them. If        you want to keep using your existing containers (for example, because        they have data volumes you want to preserve) you can migrate them with        the following command:            docker-compose migrate-to-labels        Alternatively, if you're not worried about keeping them, you can        remove them - Compose will just create new ones.            docker rm -f myapp_web_1 myapp_db_1 ...        Usage: migrate-to-labels        """        legacy.migrate_project_to_labels(project)    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 list_containers(containers):    return ", ".join(c.name for c in containers)
 |