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