예제 #1
0
 def _exec_and_log_output(self, commands: typing.List[str],
                          logfile: pathlib.Path, prefix: str):
     """Run specified commands in the container and write output to the log"""
     if not self.container:
         raise FailedTestError(
             f'{prefix}: container is not running before commands execution'
         )
     output_total = []
     for command in commands:
         output_total.append(f'    === executing command: {command} ===')
         exit_code, output = self.container.exec_run(cmd=command)
         output_total.append(output.decode('utf-8'))
         if exit_code != 0:
             log.error(
                 f'- {prefix}: command {command} have returned non-zero exit code {exit_code}'
             )
             log.error(f'Failed command stdout: {output_total[-1]}')
             logger.switch_to_custom(logfile, str(logfile.parent))
             for output in output_total:
                 log.error(str(output))
             logger.switch_to_summary()
             raise FailedTestError(
                 f'{prefix}: command {command} '
                 f'have returned non-zero exit code {exit_code}')
         self.container.reload()
         if self.container.status != 'running':
             raise FailedTestError(
                 f'{prefix}: command exit code is 0, '
                 'but container status != "running" after this command')
     logger.switch_to_custom(logfile, str(logfile.parent))
     for output in output_total:
         log.info(str(output))
     logger.switch_to_summary()
예제 #2
0
    def build_docker_image(
        self,
        dockerfile: typing.Union[str, pathlib.Path],
        tag: str,
        directory: typing.Optional[str] = None,
        build_args: typing.Optional[typing.Dict[str, str]] = None,
        logfile: typing.Optional[pathlib.Path] = None
    ) -> typing.Optional[Image]:
        """Build Docker image"""
        if not build_args:
            build_args = {}
        if not directory:
            directory = str(self.location)
        if not logfile:
            logfile = pathlib.Path(directory) / 'logs' / tag / 'build.log'

        directory = str(pathlib.Path(directory).as_posix())
        dockerfile = str(pathlib.Path(dockerfile).as_posix())

        logfile.parent.mkdir(exist_ok=True, parents=True)

        try:
            logger.switch_to_custom(logfile, str(logfile.parent))
            log.info(
                f'build command: docker build {directory} -f {dockerfile} '
                f'{"".join([f"--build-arg {k}={v} " for k, v in build_args.items()])}'
            )
            log_generator = self.client.api.build(path=directory,
                                                  tag=tag,
                                                  dockerfile=dockerfile,
                                                  rm=True,
                                                  use_config_proxy=True,
                                                  nocache=True,
                                                  pull=True,
                                                  buildargs=build_args,
                                                  decode=True)

            for line in log_generator:
                for key, value in line.items():
                    log.info(f'{key} {value}')
                    if key == 'error':
                        logger.switch_to_summary()
                        log.error(f'{value}')
                        return None
            logger.switch_to_summary()
            return self.client.images.get(tag)

        except APIError as error:
            logger.switch_to_summary()
            log.error(f'Docker server error: {error}')

        return None
예제 #3
0
    def deploy(self):
        """Push built Docker image to repo specified in CLI"""
        log.info(logger.LINE_DOUBLE)
        log.info('Publishing built Docker image...')

        for tag in self.args.tags:
            log_name = f'deploy_{tag.replace("/", "_").replace(":", "_")}.log'
            log_path_file = self.logdir / log_name
            log.info(f'Image {tag} push log location: {log_path_file}')
            logger.switch_to_custom(log_path_file)
            curr_time = timeit.default_timer()

            attempt_number = 1
            while attempt_number <= MAX_DEPLOY_RETRIES:
                log.info(f'Try deploy the image, attempt #{attempt_number}')
                attempt_number += 1
                try:
                    if self.args.registry in tag:
                        log_generator = self.docker_api.client.images.push(
                            tag, stream=True, decode=True)
                    else:
                        log_generator = self.docker_api.client.images.push(
                            self.args.registry + '/' + tag,
                            stream=True,
                            decode=True)
                    for line in log_generator:
                        for key, value in line.items():
                            if 'error' in key or 'errorDetail' in key:
                                raise FailedDeployError(f'{value}')
                            log.info(f'{value}')
                    break
                except (APIError, ReadTimeoutError) as err:
                    log.warning(
                        f'Something went wrong during pushing the image, trying again after sleeping '
                        f'{SLEEP_BETWEEN_RETRIES}s: \n\t {err}')
                    time.sleep(SLEEP_BETWEEN_RETRIES)
                    continue
            else:
                raise FailedDeployError(
                    f'Push had failed after {MAX_DEPLOY_RETRIES} attempts')
            logger.switch_to_summary()
            log.info(
                f'Push time: {format_timedelta(timeit.default_timer() - curr_time)}'
            )
            log.info('Image successfully published')
예제 #4
0
    def test_docker_image(self,
                          image: typing.Tuple[Image, str],
                          commands: typing.List[str],
                          test_name: str,
                          is_cached: bool = False,
                          **kwargs: typing.Optional[typing.Dict]):
        """Running list of commands inside the container, logging the output and handling possible exceptions"""
        if isinstance(image, Image):
            file_tag = image.tags[0].replace('/', '_').replace(':', '_')
        elif isinstance(image, str):
            file_tag = image.replace('/', '_').replace(':', '_')
        else:
            raise FailedTest(
                f'{image} is not a proper image, must be of "str" or "docker.models.images.Image"'
            )
        log_filename = f'{test_name}.log'
        logfile = pathlib.Path(
            self.location) / 'logs' / file_tag / log_filename
        run_kwargs = {
            'auto_remove': True,
            'detach': True,
            'use_config_proxy': True,
            'environment': get_system_proxy(),
            'stdin_open': True,
            'tty': True,
            'user': '******'
        }
        if kwargs is not None:
            run_kwargs.update(kwargs)

        try:
            if self.container and image not in self.container.image.tags:
                self.container.stop()
                self.container = None
            if self.container and not is_cached:
                self.container.stop()
            if not self.container or not is_cached:
                self.container = self.client.containers.run(image=image,
                                                            **run_kwargs)
        except APIError as err:
            raise FailedTest(
                f'Docker daemon API error while starting the container: {err}')

        if not self.container:
            raise FailedTest('Cannot create/start the container')

        try:
            output_total = []
            for command in commands:
                output_total.append(
                    f'    === executing command: {command} ===')
                exit_code, output = self.container.exec_run(cmd=command)
                output_total.append(output.decode('utf-8'))
                if exit_code != 0:
                    log.error(
                        f'- Test {test_name}: command {command} have returned non-zero exit code {exit_code}'
                    )
                    log.error(f'Failed command stdout: {output_total[-1]}')
                    logger.switch_to_custom(logfile, str(logfile.parent))
                    for output in output_total:
                        log.error(str(output))
                    logger.switch_to_summary()
                    raise FailedTest(
                        f'Test {test_name}: command {command} '
                        f'have returned non-zero exit code {exit_code}')
                self.container.reload()
                if self.container.status != 'running':
                    raise FailedTest(
                        f'Test {test_name}: command exit code is 0, but container status != "running" '
                        'after this command')
            logger.switch_to_custom(logfile, str(logfile.parent))
            for output in output_total:
                log.info(str(output))
            logger.switch_to_summary()

        except APIError as err:
            raise FailedTest(
                f'Docker daemon API error while executing test {test_name}: {err}'
            )