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()
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)
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)
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)
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)
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
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)
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