Ejemplo n.º 1
0
    def registered(self, driver, executorInfo, frameworkInfo, slaveInfo):

        logger.info("Setting up environment for building containers")

        # Parse the build task object
        try:
            build_task = portainer_pb2.BuildTask()
            build_task.ParseFromString(executorInfo.data)
        except Exception:
            logger.error("Failed to parse BuildTask in ExecutorInfo.data")
            raise

        self.build_task = build_task

        # Launch the docker daemon
        def launch_docker_daemon():
            logger.info("Launching docker daemon subprocess")

            env = dict(os.environ)
            env["DOCKER_DAEMON_ARGS"] = " -g %s" % (
                os.path.join(os.environ.get("MESOS_SANDBOX", os.environ["MESOS_DIRECTORY"]), "docker")
            )

            for reg in build_task.daemon.insecure_registries:
                env["DOCKER_DAEMON_ARGS"] += " --insecure-registry %s" % reg

            # Use the `wrapdocker` script included in our docker image
            proc = subprocess.Popen(["/usr/local/bin/wrapdocker"], env=env)

            self.docker = docker.Client()
            while True:
                try:
                    self.docker.ping()
                except:
                    logger.info("Waiting for docker daemon to respond to pings")
                    time.sleep(1)
                else:
                    self.docker_daemon_up = True
                    break

            proc.wait()

        if not build_task.daemon.HasField("docker_host"):
            daemon_thread = threading.Thread(target=launch_docker_daemon)
            daemon_thread.setDaemon(True)
            daemon_thread.start()
        else:
            self.docker = docker.Client(build_task.daemon.docker_host)
            self.docker_daemon_up = True
Ejemplo n.º 2
0
    def registered(self, driver, executorInfo, frameworkInfo, slaveInfo):

        logger.info("Setting up environment for building containers")

        # Parse the build task object
        try:
            build_task = portainer_pb2.BuildTask()
            build_task.ParseFromString(executorInfo.data)
        except Exception:
            logger.error("Failed to parse BuildTask in ExecutorInfo.data")
            raise

        self.build_task = build_task

        # Launch the docker daemon
        def launch_docker_daemon():
            logger.info("Launching docker daemon subprocess")

            # TODO(tarnfeld): This should be made a little more flexible
            proc = subprocess.Popen(["/usr/local/bin/wrapdocker"])
            # os.environ["MESOS_DIRECTORY"]

            self.docker = docker.Client()
            while True:
                try:
                    self.docker.ping()
                except:
                    logger.info(
                        "Waiting for docker daemon to respond to pings")
                    time.sleep(1)
                else:
                    self.docker_daemon_up = True
                    break

            proc.wait()

        if not build_task.HasField("docker_host"):
            daemon_thread = threading.Thread(target=launch_docker_daemon)
            daemon_thread.setDaemon(True)
            daemon_thread.start()
        else:
            self.docker = docker.Client(build_task.docker_host)
            self.docker_daemon_up = True
Ejemplo n.º 3
0
    def _prepare_task(self, driver, path, dockerfile, tags, offer, cpu, mem):
        """Prepare a given dockerfile build task atop the given mesos offer."""

        # Generate a task ID
        task_id = str(uuid.uuid1())
        logger.info("Preparing task %s to build %s", task_id, path)

        # Define the build that's required
        build_task = portainer_pb2.BuildTask()
        build_task.stream = self.stream

        # Create a custom docker context if there are local sources
        staging_context_path = None
        if dockerfile.has_local_sources:
            working_dir = os.path.abspath(os.path.dirname(path))

            # Generate the dockerfile build context
            _, context_path = tempfile.mkstemp()
            context = open(context_path, "w+b")

            logger.debug("Writing context tar to %s", context_path)
            context_size = self._make_build_context(context, working_dir,
                                                    dockerfile)

            # Put together the staging directory
            staging_dir = os.path.join("staging", task_id)
            context_filename = "docker_context.tar.gz"

            staging_context_path = os.path.join(staging_dir, context_filename)

            # Create the directory
            logger.debug("Task staging directory %s", staging_dir)
            self.filesystem.makedir(staging_dir, recursive=True)

            # Upload the build context (+ fancy progress bar)
            logger.info("Uploading context (%d bytes)", context_size)
            pbar = progressbar.ProgressBar(maxval=context_size, term_width=100)

            # Define a custom error handler for the async upload
            def handle_exception(e):
                logger.error("Caught exception uploading the context")
                raise e

            event = self.filesystem.setcontents_async(
                path=staging_context_path,
                data=context,
                progress_callback=pbar.update,
                finished_callback=pbar.finish,
                error_callback=handle_exception)

            # Hold up, let's wait until the upload finishes
            event.wait()

            # Close and clear up the tmp context
            logger.debug("Cleaning up local context %s", context_path)
            context.close()
            os.unlink(context_path)

            build_task.context = context_filename
        else:
            build_task.dockerfile = dockerfile.build()

        if self.docker_host:
            build_task.docker_host = self.docker_host

        # Pull out the repository from the dockerfile
        try:
            build_task.image.repository = dockerfile.get(
                "REPOSITORY", [self.repository]).next()[0]
        except (StopIteration, IndexError):
            raise ValueError("No REPOSITORY given for %s", path)

        # Pull out the registry from the dockerfile
        try:
            registry = self.push_registry.split(":")
            build_task.image.registry.hostname = registry[0]
            if len(registry) > 1:
                build_task.image.registry.port = int(registry[1])
        except ValueError:
            raise ValueError("Failed to parse REGISTRY in %s", path)

        # Add any tags
        build_task.image.tag.extend(tags)

        # Define the mesos task
        task = mesos_pb2.TaskInfo()
        task.name = "%s/%s" % (":".join(registry), build_task.image.repository)
        task.task_id.value = task_id
        task.slave_id.value = offer.slave_id.value

        # Create the executor
        args = []
        if self.verbose:
            args.append("--verbose")

        task.executor.executor_id.value = task_id
        task.executor.command.value = "./%s/bin/portainer %s build-executor" % (
            os.path.basename(
                self.executor_uri).rstrip(".tar.gz"), " ".join(args))

        # TODO(tarnfeld): Make this configurable
        # TODO(tarnfeld): Support the mesos 0.20.0 docker protobuf
        task.executor.command.container.image = "docker://jpetazzo/dind"

        # We have to mount the /var/lib/docker VOLUME inside of the sandbox
        task.executor.command.container.options.extend(["--privileged"])
        task.executor.command.container.options.extend(
            ["-v", "$MESOS_DIRECTORY/docker:/var/lib/docker"])

        task.executor.name = "build"
        task.executor.source = "portainer"

        # Configure the mesos executor with the portainer executor uri
        portainer_executor = task.executor.command.uris.add()
        portainer_executor.value = self.executor_uri

        if staging_context_path:
            # Add the docker context
            uri = task.executor.command.uris.add()
            uri.value = os.path.join(self.staging_uri, staging_context_path)
            uri.extract = False

        task.data = build_task.SerializeToString()
        task.executor.data = task.data

        # Build up the resources
        cpu_resource = task.resources.add()
        cpu_resource.name = "cpus"
        cpu_resource.type = mesos_pb2.Value.SCALAR
        cpu_resource.scalar.value = cpu

        mem_resource = task.resources.add()
        mem_resource.name = "mem"
        mem_resource.type = mesos_pb2.Value.SCALAR
        mem_resource.scalar.value = mem

        self.task_ids[task_id] = build_task

        logger.info("Prepared task %s to build %s", task_id, path)
        logger.debug("%s", build_task)

        return task
Ejemplo n.º 4
0
    def enqueue_build(self, path, tags):
        """Enqueue a dockerfile (with a set of associated tags) to build.
        """

        task_id = str(uuid.uuid1())

        logger.info("Queuing build for %s for %s", task_id, path)

        build_task = portainer_pb2.BuildTask()
        build_task.stream = self.stream
        build_task.task_id = task_id

        dockerfile = parse_dockerfile(path, registry=self.pull_registry)

        # Prepare a custom build context if there are any local sources, since they
        # will need to be shipped to the cluster
        if dockerfile.has_local_sources:
            working_dir = os.path.abspath(os.path.dirname(path))

            # Generate the dockerfile build context
            _, context_path = tempfile.mkstemp()
            context = open(context_path, "w+b")

            logger.debug("Writing context tar to %s", context_path)
            context_size = self._make_build_context(context, working_dir,
                                                    dockerfile)

            # Put together the staging directory
            staging_dir = os.path.join("staging", task_id)
            context_filename = "docker_context.tar.gz"

            staging_context_path = os.path.join(staging_dir, context_filename)

            # Create the directory
            logger.debug("Task staging directory %s", staging_dir)
            self.filesystem.makedir(staging_dir, recursive=True)

            # Upload the build context (+ fancy progress bar)
            logger.info("Uploading context (%d bytes)", context_size)
            pbar = progressbar.ProgressBar(maxval=context_size, term_width=100)

            # Define a custom error handler for the async upload
            caught_exception = threading.Event()

            def handle_exception(e):
                (_, _, tb) = sys.exc_info()
                logger.error("Caught exception uploading the context: %s" %
                             e.message)
                logger.error(traceback.format_exc(tb))
                caught_exception.set()

            event = self.filesystem.setcontents_async(
                path=staging_context_path,
                data=context,
                progress_callback=pbar.update,
                finished_callback=pbar.finish,
                error_callback=handle_exception)

            # Hold up, let's wait until the upload finishes
            event.wait()

            # Close and clear up the tmp context
            logger.debug("Cleaning up local context %s", context_path)
            context.close()
            os.unlink(context_path)

            # Check to see if we caught any exceptions while uploading the context
            if caught_exception.is_set():
                raise TaskContextException(
                    "Exception raised while uploading context")

            build_task.context = context_filename
            build_task.context_url = os.path.join(self.staging_uri,
                                                  staging_context_path)
        else:
            build_task.dockerfile = dockerfile.build()

        # Configure properties on the docker daemon
        if self.docker_host:
            build_task.daemon.docker_host = self.docker_host
        if self.insecure_registries:
            for registry in [self.pull_registry, self.push_registry]:
                if registry:
                    build_task.daemon.insecure_registries.append(registry)

        # Pull out the repository from the dockerfile
        try:
            build_task.image.repository = dockerfile.get(
                "REPOSITORY", [self.repository]).next()[0]
        except (StopIteration, IndexError):
            raise ValueError("No REPOSITORY given for %s", path)

        # Pull out the registry from the dockerfile
        try:
            registry = self.push_registry.split(":")
            build_task.image.registry.hostname = registry[0]
            if len(registry) > 1:
                build_task.image.registry.port = int(registry[1])
        except ValueError:
            raise ValueError("Failed to parse REGISTRY in %s", path)

        # Add any tags
        build_task.image.tag.extend(tags)

        with self._processing_queue:
            self.pending += 1
            self.queued_tasks.append((dockerfile, build_task))
Ejemplo n.º 5
0
    def _prepare_task(self, driver, task_id, path, dockerfile, tags, offer,
                      cpu, mem):
        """Prepare a given dockerfile build task atop the given mesos offer."""

        logger.info("Preparing task %s to build %s", task_id, path)

        # Define the build that's required
        build_task = portainer_pb2.BuildTask()
        build_task.stream = self.stream

        # Create a custom docker context if there are local sources
        staging_context_path = None
        if dockerfile.has_local_sources:
            working_dir = os.path.abspath(os.path.dirname(path))

            # Generate the dockerfile build context
            _, context_path = tempfile.mkstemp()
            context = open(context_path, "w+b")

            logger.debug("Writing context tar to %s", context_path)
            context_size = self._make_build_context(context, working_dir,
                                                    dockerfile)

            # Put together the staging directory
            staging_dir = os.path.join("staging", task_id)
            context_filename = "docker_context.tar.gz"

            staging_context_path = os.path.join(staging_dir, context_filename)

            # Create the directory
            logger.debug("Task staging directory %s", staging_dir)
            self.filesystem.makedir(staging_dir, recursive=True)

            # Upload the build context (+ fancy progress bar)
            logger.info("Uploading context (%d bytes)", context_size)
            pbar = progressbar.ProgressBar(maxval=context_size, term_width=100)

            # Define a custom error handler for the async upload
            caught_exception = threading.Event()

            def handle_exception(e):
                (_, _, tb) = sys.exc_info()
                logger.error("Caught exception uploading the context: %s" %
                             e.message)
                logger.error(traceback.format_exc(tb))
                caught_exception.set()

            event = self.filesystem.setcontents_async(
                path=staging_context_path,
                data=context,
                progress_callback=pbar.update,
                finished_callback=pbar.finish,
                error_callback=handle_exception)

            # Hold up, let's wait until the upload finishes
            event.wait()

            # Close and clear up the tmp context
            logger.debug("Cleaning up local context %s", context_path)
            context.close()
            os.unlink(context_path)

            # Check to see if we caught any exceptions while uploading the context
            if caught_exception.is_set():
                raise TaskContextException(
                    "Exception raised while uploading context")

            build_task.context = context_filename
        else:
            build_task.dockerfile = dockerfile.build()

        # Configure properties on the docker daemon
        if self.docker_host:
            build_task.daemon.docker_host = self.docker_host
        if self.insecure_registries:
            for registry in [self.pull_registry, self.push_registry]:
                if registry:
                    build_task.daemon.insecure_registries.append(registry)

        # Pull out the repository from the dockerfile
        try:
            build_task.image.repository = dockerfile.get(
                "REPOSITORY", [self.repository]).next()[0]
        except (StopIteration, IndexError):
            raise ValueError("No REPOSITORY given for %s", path)

        # Pull out the registry from the dockerfile
        try:
            registry = self.push_registry.split(":")
            build_task.image.registry.hostname = registry[0]
            if len(registry) > 1:
                build_task.image.registry.port = int(registry[1])
        except ValueError:
            raise ValueError("Failed to parse REGISTRY in %s", path)

        # Add any tags
        build_task.image.tag.extend(tags)

        # Define the mesos task
        task = mesos_pb2.TaskInfo()
        task.name = "%s/%s" % (":".join(registry), build_task.image.repository)
        task.task_id.value = task_id
        task.slave_id.value = offer.slave_id.value

        # Create the executor
        args = []
        if self.verbose:
            args.append("--verbose")

        task.executor.executor_id.value = task_id
        task.executor.command.value = "${MESOS_SANDBOX:-${MESOS_DIRECTORY}}/%s/bin/portainer %s build-executor" % (
            os.path.basename(
                self.executor_uri).rstrip(".tar.gz"), " ".join(args))

        if self.container_image:
            task.executor.container.type = mesos_pb2.ContainerInfo.DOCKER
            task.executor.container.docker.image = self.container_image
            task.executor.container.docker.privileged = True

        task.executor.name = "build"
        task.executor.source = "build %s" % (task.name)

        # Configure the mesos executor with the portainer executor uri
        portainer_executor = task.executor.command.uris.add()
        portainer_executor.value = self.executor_uri

        if staging_context_path:
            # Add the docker context
            uri = task.executor.command.uris.add()
            uri.value = os.path.join(self.staging_uri, staging_context_path)
            uri.extract = False

        task.data = build_task.SerializeToString()
        task.executor.data = task.data

        # Build up the resources we require
        cpu_resource = task.resources.add()
        cpu_resource.name = "cpus"
        cpu_resource.type = mesos_pb2.Value.SCALAR
        cpu_resource.scalar.value = cpu

        mem_resource = task.resources.add()
        mem_resource.name = "mem"
        mem_resource.type = mesos_pb2.Value.SCALAR
        mem_resource.scalar.value = mem

        self.task_ids[task_id] = build_task

        logger.info("Prepared task %s to build %s", task_id, path)
        logger.debug("%s", build_task)

        return task