Esempio n. 1
0
    def test_invalid_arguments_passed_to_container_to_handle(self, argv):
        with patch('cdflow.docker') as docker, \
                patch('cdflow.os') as os, \
                patch('cdflow.open') as open_:

            account_id = '1234567890'
            config_file = MagicMock(spec=TextIOWrapper)
            config_file.read.return_value = json.dumps(
                {'platform_config': {
                    'account_id': account_id
                }})
            open_.return_value.__enter__.return_value = config_file

            error = ContainerError(container=CDFLOW_IMAGE_ID,
                                   exit_status=1,
                                   command=argv,
                                   image=CDFLOW_IMAGE_ID,
                                   stderr='help text')
            docker.from_env.return_value.containers.run.side_effect = error
            os.path.abspath.return_value = '/'
            exit_status = main(argv)

        assert exit_status == 1

        docker.from_env.return_value.containers.run.assert_called_once_with(
            CDFLOW_IMAGE_ID,
            command=argv,
            environment=ANY,
            detach=True,
            volumes=ANY,
            working_dir=ANY)
Esempio n. 2
0
    def read(cls,
             container: Container,
             command: str = None,
             with_ext: bool = True) -> Iterable[str]:
        """Reads connector's logs per line"""
        buffer = b""
        exception = ""
        line = ""
        for chunk in container.logs(stdout=True,
                                    stderr=True,
                                    stream=True,
                                    follow=True):

            buffer += chunk
            while True:
                # every chunk can include several lines
                found = buffer.find(b"\n")
                if found <= -1:
                    break

                line = buffer[:found + 1].decode("utf-8")
                if len(exception) > 0 or line.startswith(
                        "Traceback (most recent call last)"):
                    exception += line
                else:
                    yield line
                buffer = buffer[found + 1:]

        if buffer:
            # send the latest chunk if exists
            line = buffer.decode("utf-8")
            if exception:
                exception += line
            else:
                yield line
        try:
            exit_status = container.wait()
            container.remove()
        except NotFound as err:
            logging.error(f"Waiting error: {err}, logs: {exception or line}")
            raise
        if exit_status["StatusCode"]:
            error = exit_status["Error"] or exception or line
            logging.error(f"Docker container was failed, "
                          f'code {exit_status["StatusCode"]}, error:\n{error}')
            if with_ext:
                raise ContainerError(
                    container=container,
                    exit_status=exit_status["StatusCode"],
                    command=command,
                    image=container.image,
                    stderr=error,
                )
Esempio n. 3
0
    def test_container_without_stderr(self):
        """The massage does not contain stderr"""
        client = make_fake_client()
        container = client.containers.get(FAKE_CONTAINER_ID)
        command = "echo Hello World"
        exit_status = 42
        image = FAKE_IMAGE_ID
        stderr = None

        err = ContainerError(container, exit_status, command, image, stderr)
        msg = ("Command '{}' in image '{}' returned non-zero exit status {}"
               ).format(command, image, exit_status, stderr)
        assert str(err) == msg
Esempio n. 4
0
    def read(cls,
             container: Container,
             command: str = None,
             with_ext: bool = True) -> Iterable[str]:
        """Reads connector's logs per line"""
        buffer = b""
        has_exception = False
        for chunk in container.logs(stdout=True,
                                    stderr=True,
                                    stream=True,
                                    follow=True):
            buffer += chunk
            found = buffer.find(b"\n")
            if found <= -1:
                continue
            line = buffer[:found].decode("utf-8")
            if has_exception or "Traceback (most recent call last)" in line:
                has_exception = True
            else:
                yield line
                buffer = buffer[found + 1:]
        if not has_exception and buffer:
            yield buffer.decode("utf-8")

        exit_status = container.wait()
        if exit_status["StatusCode"]:
            error = buffer.decode(
                "utf-8") if has_exception else exit_status["Error"]
            logging.error(f"Docker container was failed, "
                          f'code {exit_status["StatusCode"]}, error:\n{error}')
            if with_ext:
                raise ContainerError(
                    container=container,
                    exit_status=exit_status["StatusCode"],
                    command=command,
                    image=container.image,
                    stderr=error,
                )
Esempio n. 5
0
    def run(self,
            cmd,
            config=None,
            state=None,
            catalog=None,
            **kwargs) -> Iterable[AirbyteMessage]:
        self._runs += 1
        volumes = self._prepare_volumes(config, state, catalog)
        logging.info("Docker run: \n%s\ninput: %s\noutput: %s", cmd,
                     self.input_folder, self.output_folder)
        try:
            logs = self._client.containers.run(image=self._image,
                                               command=cmd,
                                               working_dir="/data",
                                               volumes=volumes,
                                               network="host",
                                               stdout=True,
                                               stderr=True,
                                               **kwargs)
        except ContainerError as err:
            # beautify error from container
            patched_error = ContainerError(container=err.container,
                                           exit_status=err.exit_status,
                                           command=err.command,
                                           image=err.image,
                                           stderr=err.stderr.decode())
            raise patched_error from None  # get rid of any previous exception stack

        with open(str(self.output_folder / "raw"), "wb+") as f:
            f.write(logs)

        for line in logs.decode("utf-8").splitlines():
            try:
                yield AirbyteMessage.parse_raw(line)
            except ValidationError as exc:
                logging.warning("Unable to parse connector's output %s", exc)
Esempio n. 6
0
        image=ANY,
        command=ANY,
        auto_remove=True,
        volumes={str(tmp_path): {
                     "bind": "/project",
                     "mode": "rw"
                 }},
        stream=True,
        user=ANY,
    )


@pytest.mark.parametrize(
    "exception",
    [
        lambda: ContainerError("abcde", 255, "/bin/false", "image", ""),
        ImageLoadError,
        lambda: APIError("500"),
        lambda: RequestsConnectionError(
            "Connection aborted.",
            ConnectionRefusedError(61, "Connection refused")),
    ],
)
def test__docker_build_bad_path(plugin, tmp_path, exception):
    patch_sdist = patch.object(PythonLanguagePlugin,
                               "_check_for_support_lib_sdist")
    patch_from_env = patch("rpdk.python.codegen.docker.from_env",
                           autospec=True)

    with patch_sdist as mock_sdist, patch_from_env as mock_from_env:
        mock_run = mock_from_env.return_value.containers.run
Esempio n. 7
0
    def run(self):
        self.before_run()
        # get image if missing
        if self.force_pull or len(self._client.images(name=self._image)) == 0:
            logger.info("Pulling docker image " + self._image)
            try:
                for logline in self._client.pull(self._image, stream=True):
                    logger.debug(logline.decode("utf-8"))
            except APIError as e:
                self.__logger.warning("Error in Docker API: " + e.explanation)
                raise

        # remove clashing container if a container with the same name exists
        if self.auto_remove and self.name:
            try:
                self._client.remove_container(self.name, force=True)
            except APIError as e:
                self.__logger.warning("Ignored error in Docker API: " +
                                      e.explanation)

        # run the container
        try:
            logger.debug("Creating image: %s command: %s volumes: %s" %
                         (self._image, self.command, self._binds))

            host_config = self._client.create_host_config(
                binds=self._binds,
                network_mode=self.network_mode,
                auto_remove=self.auto_remove,
                **self.host_config_options)

            container = self._client.create_container(
                self._image,
                command=self.command,
                name=self.name,
                environment=self.environment,
                volumes=self._volumes,
                host_config=host_config,
                **self.container_options)

            self._client.start(container["Id"])

            exit_status = self._client.wait(container["Id"])
            # docker-py>=3.0.0 returns a dict instead of the status code directly
            if type(exit_status) is dict:
                exit_status = exit_status["StatusCode"]

            if exit_status != 0:
                stdout = False
                stderr = True
                error = self._client.logs(container["Id"],
                                          stdout=stdout,
                                          stderr=stderr)
            if self.auto_remove:
                try:
                    self._client.remove_container(container["Id"], force=True)
                except docker.errors.APIError:
                    self.__logger.warning("Container " + container["Id"] +
                                          " could not be removed")
            if exit_status != 0:
                raise ContainerError(container, exit_status, self.command,
                                     self._image, error)

        except ContainerError as e:
            # catch non zero exti status and return it
            container_name = ""
            if self.name:
                container_name = self.name
            try:
                message = e.message
            except AttributeError:
                message = str(e)
            self.__logger.error("Container " + container_name +
                                " exited with non zero code: " + message)
            raise
        except ImageNotFound as e:
            self.__logger.error("Image " + self._image + " not found")
            raise
        except APIError as e:
            self.__logger.error("Error in Docker API: " + e.explanation)
            raise

        self.after_run()
Esempio n. 8
0
 def raise_containererror(*args, **kwargs):
     raise ContainerError('container', 'exit_status', 'command',
                          'image', 'stderr')
Esempio n. 9
0
    def run(self):

        # get image if missing
        if self.force_pull or len(self._client.images(name=self._image)) == 0:
            logger.info('Pulling docker image ' + self._image)
            try:
                for logline in self._client.pull(self._image, stream=True):
                    logger.debug(logline.decode('utf-8'))
            except APIError as e:
                self.__logger.warning("Error in Docker API: " + e.explanation)
                raise

        # remove clashing container if a container with the same name exists
        if self.auto_remove and self.name:
            try:
                self._client.remove_container(self.name, force=True)
            except APIError as e:
                self.__logger.warning("Ignored error in Docker API: " +
                                      e.explanation)

        # run the container
        try:
            logger.debug('Creating image: %s command: %s volumes: %s' %
                         (self._image, self.command, self._binds))

            host_config = self._client.create_host_config(
                binds=self._binds, network_mode=self.network_mode)

            container = self._client.create_container(
                self._image,
                command=self.command,
                name=self.name,
                environment=self.environment,
                volumes=self._volumes,
                host_config=host_config,
                **self.container_options)
            self._client.start(container['Id'])

            exit_status = self._client.wait(container['Id'])
            if exit_status != 0:
                stdout = False
                stderr = True
                error = self._client.logs(container['Id'],
                                          stdout=stdout,
                                          stderr=stderr)
            if self.auto_remove:
                try:
                    self._client.remove_container(container['Id'])
                except docker.errors.APIError:
                    self.__logger.warning("Container " + container['Id'] +
                                          " could not be removed")
            if exit_status != 0:
                raise ContainerError(container, exit_status, self.command,
                                     self._image, error)

        except ContainerError as e:
            # catch non zero exti status and return it
            container_name = ''
            if self.name:
                container_name = self.name
            try:
                message = e.message
            except:
                message = str(e)
            self.__logger.error("Container " + container_name +
                                " exited with non zero code: " + message)
            raise
        except ImageNotFound as e:
            self.__logger.error("Image " + self._image + " not found")
            raise
        except APIError as e:
            self.__logger.error("Error in Docker API: " + e.explanation)
            raise

        # delete temp dir
        filesys = LocalFileSystem()
        if self.mount_tmp and filesys.exists(self._host_tmp_dir):
            filesys.remove(self._host_tmp_dir, recursive=True)
Esempio n. 10
0
        options["volumes"] = {}
    volumes = options["volumes"]
    volumes[tempdir] = {"bind": "/run", "mode": "rw"}
    if "working_dir" not in options:
        options["working_dir"] = "/run"
    full_docker_command = "bash DOCKER-COMMAND"
    from docker.types import LogConfig
    container = docker_client.containers.create(docker_image,
                                                full_docker_command, **options)
    stdout0 = container.attach(stdout=True, stderr=False, stream=True)
    container.start()
    exit_status = container.wait()['StatusCode']
    container.remove()

    if exit_status != 0:
        from docker.errors import ContainerError
        stderr = container.logs(stdout=False, stderr=True)
        raise ContainerError(container, exit_status, full_docker_command,
                             docker_image, stderr)

    stdout = None if stdout0 is None else b''.join([line for line in stdout0])
    os.chdir(cwd)
    with open("test-docker-api-RESULT.npy", "wb") as f:
        f.write(stdout)
    import numpy as np
    np.save("test-docker-api-ORIGINAL.npy", np.arange(12) * 3)

finally:
    com = "rm -rf {}".format(os.path.abspath(tempdir))
    os.system(com)