Exemplo n.º 1
0
    def push_or_pull(self, conf, action=None, ignore_missing=False):
        """Push or pull this image"""
        if action not in ("push", "pull"):
            raise ProgrammerError(
                "Should have called push_or_pull with action to either push or pull, got {0}"
                .format(action))

        if not conf.image_index:
            raise BadImage("Can't push without an image_index configuration",
                           image=conf.name)

        sync_stream = SyncProgressStream()
        for line in getattr(conf.harpoon.docker_context,
                            action)(conf.image_name, stream=True):
            try:
                sync_stream.feed(line)
            except Failure as error:
                if ignore_missing and action == "pull":
                    log.error(
                        "Failed to %s an image\timage=%s\timage_name=%s\tmsg=%s",
                        action, conf.name, conf.image_name, error)
                    break
                else:
                    raise FailedImage("Failed to {0} an image".format(action),
                                      image=conf.name,
                                      image_name=conf.image_name,
                                      msg=error)
            except Unknown as error:
                log.warning("Unknown line\tline=%s", error)

            for part in sync_stream.printable():
                sys.stdout.write(part.encode('utf-8', 'replace'))
            sys.stdout.flush()
Exemplo n.º 2
0
    def build_and_run(self, images):
        """Make this image and run it"""
        Builder().make_image(self, images)

        try:
            Runner().run_container(self, images)
        except DockerAPIError as error:
            raise BadImage("Failed to start the container", error=error)
Exemplo n.º 3
0
 def run_deps(self, conf, images):
     """Start containers for all our dependencies"""
     for dependency_name, detached in conf.dependency_images():
         try:
             self.run_container(images[dependency_name],
                                images,
                                detach=detached,
                                dependency=True)
         except Exception as error:
             raise BadImage("Failed to start dependency container",
                            image=conf.name,
                            dependency=dependency_name,
                            error=error)
Exemplo n.º 4
0
    def start_container(self,
                        conf,
                        tty=True,
                        detach=False,
                        is_dependency=False,
                        no_intervention=False):
        """Start up a single container"""
        # Make sure we can bind to our specified ports!
        if not conf.harpoon.docker_context.base_url.startswith("http"):
            self.find_bound_ports(conf.ports)

        container_id = conf.container_id
        container_name = conf.container_name
        log.info("Starting container %s (%s)", container_name, container_id)

        try:
            if not detach and not is_dependency:
                self.start_tty(conf,
                               interactive=tty,
                               **conf.other_options.start)
            else:
                conf.harpoon.docker_context.start(container_id,
                                                  **conf.other_options.start)
        except docker.errors.APIError as error:
            if str(error).startswith("404 Client Error: Not Found"):
                log.error("Container died before we could even get to it...")

        inspection = None
        if not detach and not is_dependency:
            inspection = self.get_exit_code(conf)

        if inspection and not no_intervention:
            if not inspection["State"][
                    "Running"] and inspection["State"]["ExitCode"] != 0:
                self.stage_run_intervention(conf)
                raise BadImage("Failed to run container",
                               container_id=container_id,
                               container_name=container_name,
                               reason="nonzero exit code after launch")

        if not is_dependency and conf.harpoon.intervene_afterwards and not no_intervention:
            self.stage_run_intervention(conf, just_do_it=True)
Exemplo n.º 5
0
    def stop_container(self,
                       conf,
                       fail_on_bad_exit=False,
                       fail_reason=None,
                       tag=None,
                       remove_volumes=False):
        """Stop some container"""
        stopped = False
        container_id = conf.container_id
        if not container_id:
            return

        container_name = conf.container_name
        stopped, exit_code = self.is_stopped(conf, container_id)

        if stopped:
            if exit_code != 0 and fail_on_bad_exit:
                if not conf.harpoon.interactive:
                    print_logs = True
                else:
                    hp.write_to(conf.harpoon.stdout, "!!!!\n")
                    hp.write_to(
                        conf.harpoon.stdout,
                        "Container had already exited with a non zero exit code\tcontainer_name={0}\tcontainer_id={1}\texit_code={2}\n"
                        .format(container_name, container_id, exit_code))
                    hp.write_to(
                        conf.harpoon.stdout,
                        "Do you want to see the logs from this container?\n")
                    conf.harpoon.stdout.flush()
                    answer = input("[y]: ")
                    print_logs = not answer or answer.lower().startswith("y")

                if print_logs:
                    hp.write_to(
                        conf.harpoon.stdout,
                        "=================== Logs for failed container {0} ({1})\n"
                        .format(container_id, container_name))
                    for line in conf.harpoon.docker_context.logs(
                            container_id).split("\n"):
                        hp.write_to(conf.harpoon.stdout, "{0}\n".format(line))
                    hp.write_to(
                        conf.harpoon.stdout,
                        "------------------- End logs for failed container\n")
                fail_reason = fail_reason or "Failed to run container"
                raise BadImage(fail_reason,
                               container_id=container_id,
                               container_name=container_name)
        else:
            try:
                log.info("Killing container %s:%s", container_name,
                         container_id)
                conf.harpoon.docker_context.kill(container_id, 9)
            except DockerAPIError:
                pass
            self.wait_till_stopped(
                conf,
                container_id,
                timeout=10,
                message=
                "waiting for container to die\tcontainer_name={0}\tcontainer_id={1}"
                .format(container_name, container_id))

        if tag:
            log.info("Tagging a container\tcontainer_id=%s\ttag=%s",
                     container_id, tag)
            new_id = conf.harpoon.docker_context.commit(container_id)["Id"]
            conf["committed"] = new_id
            if tag is not True:
                the_tag = "latest" if conf.tag is NotSpecified else conf.tag
                conf.harpoon.docker_context.tag(new_id,
                                                repository=tag,
                                                tag=the_tag,
                                                force=True)

        mounts = []
        if remove_volumes:
            inspection = conf.harpoon.docker_context.inspect_container(
                container_id)
            if "Mounts" in inspection:
                for m in inspection["Mounts"]:
                    if "Name" in m:
                        mounts.append(m['Name'])
            else:
                log.warning("Your docker can't inspect and delete volumes :(")

        if not conf.harpoon.no_cleanup:
            log.info("Removing container %s:%s", container_name, container_id)
            for _ in until(
                    timeout=10,
                    action=
                    "removing container\tcontainer_name={0}\tcontainer_id={1}".
                    format(container_name, container_id)):
                try:
                    conf.harpoon.docker_context.remove_container(container_id)
                    break
                except socket.timeout:
                    break
                except (ValueError, DockerAPIError) as error:
                    log.warning(
                        "Failed to remove container\tcontainer_id=%s\terror=%s",
                        container_id, error)

        for mount in mounts:
            try:
                log.info("Cleaning up volume {0}".format(mount))
                conf.harpoon.docker_context.remove_volume(mount)
            except DockerAPIError as error:
                log.warning("Failed to cleanup volume\tvolume=%s\terror=%s",
                            mount, error)
Exemplo n.º 6
0
    def create_container(self, conf, detach, tty):
        """Create a single container"""

        name = conf.name
        image_name = conf.image_name
        container_name = conf.container_name

        with conf.assumed_role():
            env = dict(e.pair for e in conf.env)

        links = [link.pair for link in conf.links]
        binds = conf.volumes.binds
        command = conf.formatted_command
        volume_names = conf.volumes.volume_names
        volumes_from = list(conf.volumes.share_with_names)
        no_tty_option = conf.no_tty_option

        ports = [p.container_port.port_pair for p in conf.ports]
        port_bindings = self.exposed(conf.ports)

        uncreated = []
        for name in binds:
            if not os.path.exists(name):
                log.info("Making volume for mounting\tvolume=%s", name)
                try:
                    os.makedirs(name)
                except OSError as error:
                    uncreated.append((name, error))
        if uncreated:
            raise BadOption("Failed to create some volumes on the host",
                            uncreated=uncreated)

        log.info(
            "Creating container from %s\timage=%s\tcontainer_name=%s\ttty=%s",
            image_name, name, container_name, tty)
        if binds:
            log.info("\tUsing volumes\tvolumes=%s", volume_names)
        if env:
            log.info("\tUsing environment\tenv=%s", sorted(env.keys()))
        if links:
            log.info("\tLinks: %s", links)
        if ports:
            log.info("\tUsing ports\tports=%s", ports)
        if port_bindings:
            log.info("\tPort bindings: %s", port_bindings)
        if volumes_from:
            log.info("\tVolumes from: %s", volumes_from)

        host_config = conf.harpoon.docker_context.create_host_config(
            links=links,
            binds=binds,
            volumes_from=volumes_from,
            port_bindings=port_bindings,
            devices=conf.devices,
            lxc_conf=conf.lxc_conf,
            privileged=conf.privileged,
            restart_policy=conf.restart_policy,
            dns=conf.network.dns,
            dns_search=conf.network.dns_search,
            extra_hosts=conf.network.extra_hosts,
            network_mode=conf.network.network_mode,
            publish_all_ports=conf.network.publish_all_ports,
            cap_add=conf.cpu.cap_add,
            cap_drop=conf.cpu.cap_drop,
            mem_limit=conf.cpu.mem_limit,
            memswap_limit=conf.cpu.memswap_limit,
            ulimits=conf.ulimits,
            read_only=conf.read_only_rootfs,
            log_config=conf.log_config,
            security_opt=conf.security_opt,
            **conf.other_options.host_config)

        container_id = conf.harpoon.docker_context.create_container(
            image_name,
            name=container_name,
            detach=detach,
            command=command,
            volumes=volume_names,
            environment=env,
            tty=False if no_tty_option else tty,
            user=conf.user,
            ports=ports,
            stdin_open=tty,
            dns=conf.network.dns,
            hostname=conf.network.hostname,
            domainname=conf.network.domainname,
            network_disabled=conf.network.disabled,
            cpuset=conf.cpu.cpuset,
            cpu_shares=conf.cpu.cpu_shares,
            host_config=host_config,
            **conf.other_options.create)

        if isinstance(container_id, dict):
            if "errorDetail" in container_id:
                raise BadImage("Failed to create container",
                               image=name,
                               error=container_id["errorDetail"])
            container_id = container_id["Id"]

        return container_id
Exemplo n.º 7
0
    def wait_for_deps(self, conf, images):
        """Wait for all our dependencies"""
        from harpoon.option_spec.image_objs import WaitCondition
        ctxt = conf.harpoon.docker_context_maker()

        waited = set()
        last_attempt = {}
        dependencies = set(dep for dep, _ in conf.dependency_images())

        # Wait conditions come from dependency_options first
        # Or if none specified there, they come from the image itself
        wait_conditions = {}
        for dependency in dependencies:
            if conf.dependency_options is not NotSpecified and dependency in conf.dependency_options and conf.dependency_options[
                    dependency].wait_condition is not NotSpecified:
                wait_conditions[dependency] = conf.dependency_options[
                    dependency].wait_condition
            elif images[dependency].wait_condition is not NotSpecified:
                wait_conditions[dependency] = images[dependency].wait_condition

        if not wait_conditions:
            return

        start = time.time()
        while True:
            this_round = []
            for dependency in dependencies:
                if dependency in waited:
                    continue

                image = images[dependency]
                if dependency in wait_conditions:
                    done = self.wait_for_dep(ctxt, image,
                                             wait_conditions[dependency],
                                             start,
                                             last_attempt.get(dependency))
                    this_round.append(done)
                    if done is True:
                        waited.add(dependency)
                    elif done is False:
                        last_attempt[dependency] = time.time()
                    elif done is WaitCondition.Timedout:
                        log.warning(
                            "Stopping dependency because it timedout waiting\tcontainer_id=%s",
                            image.container_id)
                        self.stop_container(image)
                else:
                    waited.add(dependency)

            if set(this_round) != set([WaitCondition.KeepWaiting]):
                if dependencies - waited == set():
                    log.info("Finished waiting for dependencies")
                    break
                else:
                    log.info("Still waiting for dependencies\twaiting_on=%s",
                             list(dependencies - waited))

                couldnt_wait = set()
                container_ids = {}
                for dependency in dependencies:
                    if dependency in waited:
                        continue

                    image = images[dependency]
                    if image.container_id is None:
                        stopped = True
                        if dependency not in container_ids:
                            available = sorted([
                                i for i in available if "/{0}".format(
                                    image.container_name) in i["Names"]
                            ],
                                               key=lambda i: i["Created"])
                            if available:
                                container_ids[dependency] = available[0]["Id"]
                    else:
                        if dependency not in container_ids:
                            container_ids[dependency] = image.container_id
                        stopped, _ = self.is_stopped(image, image.container_id)

                    if stopped:
                        couldnt_wait.add(dependency)

                if couldnt_wait:
                    for container in couldnt_wait:
                        if container not in images or container not in container_ids:
                            continue
                        image = images[container]
                        container_id = container_ids[container]
                        container_name = image.container_name
                        hp.write_to(
                            conf.harpoon.stdout,
                            "=================== Logs for failed container {0} ({1})\n"
                            .format(container_id, container_name))
                        for line in conf.harpoon.docker_context.logs(
                                container_id).split("\n"):
                            hp.write_to(conf.harpoon.stdout,
                                        "{0}\n".format(line))
                        hp.write_to(
                            conf.harpoon.stdout,
                            "------------------- End logs for failed container\n"
                        )
                    raise BadImage(
                        "One or more of the dependencies stopped running whilst waiting for other dependencies",
                        stopped=list(couldnt_wait))

            time.sleep(0.1)
Exemplo n.º 8
0
    def push_or_pull(self, conf, action=None, ignore_missing=False):
        """Push or pull this image"""
        if action not in ("push", "pull"):
            raise ProgrammerError(
                "Should have called push_or_pull with action to either push or pull, got {0}"
                .format(action))

        if not conf.image_index:
            raise BadImage(
                "Can't {0} without an image_index configuration".format(
                    action),
                image=conf.name)

        if conf.image_name == "scratch":
            log.warning(
                "Not pulling/pushing scratch, this is a reserved image!")
            return

        sync_stream = SyncProgressStream()

        # Login before pulling or pushing
        conf.login(conf.image_name, is_pushing=action == 'push')

        for attempt in range(3):
            try:
                for line in getattr(conf.harpoon.docker_context, action)(
                        conf.image_name,
                        tag=None if conf.tag is NotSpecified else conf.tag,
                        stream=True):
                    try:
                        sync_stream.feed(line)
                    except Failure as error:
                        if ignore_missing and action == "pull":
                            log.error(
                                "Failed to %s an image\timage=%s\timage_name=%s\tmsg=%s",
                                action, conf.name, conf.image_name, error)
                            break
                        else:
                            raise FailedImage(
                                "Failed to {0} an image".format(action),
                                image=conf.name,
                                image_name=conf.image_name,
                                msg=error)
                    except Unknown as error:
                        log.warning("Unknown line\tline=%s", error)

                    for part in sync_stream.printable():
                        if six.PY3:
                            conf.harpoon.stdout.write(part)
                        else:
                            conf.harpoon.stdout.write(
                                part.encode('utf-8', 'replace'))
                    conf.harpoon.stdout.flush()

                # And stop the loop!
                break

            except KeyboardInterrupt:
                raise
            except FailedImage as error:
                log.exception(error)
                if action == "push" or attempt == 2:
                    raise