def checkout_git_repo(git_uri, git_ref, git_branch=None): tmpdir = tempfile.mkdtemp() repo_path = os.path.join(tmpdir, "repo") try: try: # when you clone into an empty directory and cloning process fails # git will remove the empty directory (why?!) args = ['git', 'clone', git_uri] if git_branch: args += ['-b', git_branch] args.append(repo_path) run_command(args) except OsbsException as ex: raise OsbsException("Unable to clone git repo '%s' " "branch '%s'" % (git_uri, git_branch), cause=ex, traceback=sys.exc_info()[2]) # Find the specific ref we want try: run_command(['git', 'reset', '--hard'], cwd=repo_path) run_command(['git', 'checkout', git_ref], cwd=repo_path) except OsbsException as ex: raise OsbsException("Unable to reset branch to '%s'" % git_ref, cause=ex, traceback=sys.exc_info()[2]) yield repo_path finally: shutil.rmtree(tmpdir)
def _check_repo_file_exists_with_expected_filename(self, expected_filename, possible_filename_typos): """ Checks if a file with given filename exists in repo :param str expected_filename: Expected filename to lookup in the repo :param set possible_filename_typos: Set of possible typos for expected_filename :return: boolean stating wheter expected_filename exists in repo :rtype bool :raises OsbsException: if any filename from possible_filename_typos exists in repo """ expected_file_path = os.path.join(self.dir_path, expected_filename) wrong_filename = '' for possible_filename_typo in possible_filename_typos: path = os.path.join(self.dir_path, possible_filename_typo) if os.path.exists(path): wrong_filename = possible_filename_typo break if os.path.exists(expected_file_path) and wrong_filename: msg = ('This repo contains both {expected_filename} and {wrong_filename} ' 'Please remove {wrong_filename}' .format(expected_filename=expected_filename, wrong_filename=wrong_filename)) raise OsbsException(msg) elif wrong_filename: msg = ('Repo contains wrong filename: {wrong_filename}, expected: {expected_filename}' .format(expected_filename=expected_filename, wrong_filename=wrong_filename)) raise OsbsException(msg) elif os.path.exists(expected_file_path): return True return False
def start_pipeline_run(self): if not self.input_data: raise OsbsException( "No input data provided for pipeline run to start") run_name = self.input_data.get('metadata', {}).get('name') labels = self.input_data.get('metadata', {}).get('labels', {}) if labels: sanitized_labels = { k: sanitize_strings_for_openshift(v) for k, v in labels.items() } self.input_data['metadata']['labels'] = sanitized_labels if run_name != self.pipeline_run_name: msg = f"Pipeline run name provided '{self.pipeline_run_name}' is different " \ f"than in input data '{run_name}'" raise OsbsException(msg) url = self.os.build_url(self.api_path, self.api_version, "pipelineruns") response = self.os.post( url, data=json.dumps(self.input_data), headers={ "Content-Type": "application/json", "Accept": "application/json" }, ) return response.json()
def get_build_config_by_labels_filtered(self, label_selectors, filter_key, filter_value): """ Returns a build config matching the given label selectors, filtering against another predetermined value. This method will raise OsbsException if not exactly one build config is found after filtering. """ items = self.get_all_build_configs_by_labels(label_selectors) if filter_value is not None: build_configs = [] for build_config in items: match_value = graceful_chain_get(build_config, *filter_key.split('.')) if filter_value == match_value: build_configs.append(build_config) items = build_configs if not items: raise OsbsException("Build config not found for labels: %r" % (label_selectors, )) if len(items) > 1: raise OsbsException( "More than one build config found for labels: %r" % (label_selectors, )) return items[0]
def _checks_for_flatpak(self, flatpak, repo_info): if flatpak and not repo_info.configuration.is_flatpak: raise OsbsException( "Flatpak build, " "but repository doesn't have a container.yaml with a flatpak: section" ) if not flatpak and repo_info.configuration.is_flatpak: raise OsbsException( "Not a flatpak build, " "but repository has a container.yaml with a flatpak: section")
def test_orchestrate_build_failed_to_list_builds(tmpdir, fail_at): workflow = mock_workflow(tmpdir) mock_osbs() # Current builds is a constant 2 mock_reactor_config( tmpdir, { 'x86_64': [{ 'name': 'spam', 'max_concurrent_builds': 5 }, { 'name': 'eggs', 'max_concurrent_builds': 5 }], }) flexmock_chain = flexmock(OSBS).should_receive('list_builds').and_raise( OsbsException("foo")) if fail_at == 'all': flexmock_chain.and_raise(OsbsException("foo")) if fail_at == 'first': flexmock_chain.and_return(['a', 'b']) if fail_at == 'build_canceled': flexmock_chain.and_raise(OsbsException(cause=BuildCanceledException())) runner = BuildStepPluginsRunner( workflow.builder.tasker, workflow, [{ 'name': OrchestrateBuildPlugin.key, 'args': { 'platforms': ['x86_64'], 'build_kwargs': make_worker_build_kwargs(), 'osbs_client_config': str(tmpdir), 'find_cluster_retry_delay': .1, 'max_cluster_fails': 2 } }]) if fail_at == 'first': build_result = runner.run() assert not build_result.is_failed() annotations = build_result.annotations assert annotations['worker-builds']['x86_64']['build'][ 'cluster-url'] == 'https://eggs.com/' else: build_result = runner.run() assert build_result.is_failed() if fail_at == 'all': assert 'Could not find appropriate cluster for worker build.' \ in build_result.fail_reason elif fail_at == 'build_canceled': assert 'BuildCanceledException()' in str(exc)
def get_pod_for_build(self, build_id): """ :return: PodResponse object for pod relating to the build """ pods = self.os.list_pods(label='openshift.io/build.name=%s' % build_id) serialized_response = pods.json() pod_list = [PodResponse(pod) for pod in serialized_response["items"]] if not pod_list: raise OsbsException("No pod for build") elif len(pod_list) != 1: raise OsbsException("Only one pod expected but %d returned", len(pod_list)) return pod_list[0]
def reset_git_repo(target_dir, git_reference, retry_depth=None): """ hard reset git clone in target_dir to given git_reference :param target_dir: str, filesystem path where the repo is cloned :param git_reference: str, any valid git reference :param retry_depth: int, if the repo was cloned with --shallow, this is the expected depth of the commit :return: str and int, commit ID of HEAD and commit depth of git_reference """ deepen = retry_depth or 0 base_commit_depth = 0 for _ in range(GIT_FETCH_RETRY): try: if not deepen: cmd = ['git', 'rev-list', '--count', git_reference] base_commit_depth = int( subprocess.check_output(cmd, cwd=target_dir)) - 1 cmd = ["git", "reset", "--hard", git_reference] logger.debug("Resetting current HEAD: '%s'", cmd) subprocess.check_call(cmd, cwd=target_dir) break except subprocess.CalledProcessError: if not deepen: raise OsbsException('cannot find commit %s in repo %s' % (git_reference, target_dir)) deepen *= 2 cmd = ["git", "fetch", "--depth", str(deepen)] subprocess.check_call(cmd, cwd=target_dir) logger.debug("Couldn't find commit %s, increasing depth with '%s'", git_reference, cmd) else: raise OsbsException('cannot find commit %s in repo %s' % (git_reference, target_dir)) cmd = ["git", "rev-parse", "HEAD"] logger.debug("getting SHA-1 of provided ref '%s'", git_reference) commit_id = subprocess.check_output(cmd, cwd=target_dir, universal_newlines=True) commit_id = commit_id.strip() logger.info("commit ID = %s", commit_id) final_commit_depth = None if not deepen: cmd = ['git', 'rev-list', '--count', 'HEAD'] final_commit_depth = int(subprocess.check_output( cmd, cwd=target_dir)) - base_commit_depth return commit_id, final_commit_depth
def request(self, url, *args, **kwargs): try: stream = HttpStream(url, *args, verbose=self.verbose, **kwargs) if kwargs.get('stream', False): return stream with stream as s: # joining at once is much faster than doing += in a loop all_chunks = list(s.iter_chunks()) content = ''.join(all_chunks) return HttpResponse(s.status_code, s.headers, content) except pycurl.error as ex: code = ex.args[0] try: message = ex.args[1] except IndexError: # happened on rhel 6 message = "" if code in PYCURL_NETWORK_CODES: raise OsbsNetworkException(url, message, code, *ex.args[2:], cause=ex, traceback=sys.exc_info()[2]) raise OsbsException(cause=ex, traceback=sys.exc_info()[2])
def update_labels(self, labels): data = copy.deepcopy(self.minimal_data) sanitized_labels = { k: sanitize_strings_for_openshift(v) for k, v in labels.items() } data['metadata']['labels'] = sanitized_labels response = self.os.patch( self.pipeline_run_url, data=json.dumps(data), headers={ "Content-Type": "application/merge-patch+json", "Accept": "application/json", }, ) msg = f"update labels on pipeline run '{self.pipeline_run_name}'" exc_msg = f"Can't update labels on pipeline run '{self.pipeline_run_name}', " \ f"because it doesn't exist" response_json = self._check_response(response, msg) if not response_json: raise OsbsException(exc_msg) return response_json
def create_build(self, **kwargs): """ take input args, create build request from provided build type and submit the build :param kwargs: keyword args for build :return: instance of BuildRequest """ build_type = self.build_conf.get_build_type() if build_type in (PROD_BUILD_TYPE, PROD_WITHOUT_KOJI_BUILD_TYPE, PROD_WITH_SECRET_BUILD_TYPE): kwargs.setdefault('git_branch', None) kwargs.setdefault('target', None) return self.create_prod_build(**kwargs) elif build_type == SIMPLE_BUILD_TYPE: # Only Prod Build type cares about potential koji scratch builds try: kwargs.pop("scratch") except KeyError: pass return self.create_simple_build(**kwargs) elif build_type == PROD_WITH_SECRET_BUILD_TYPE: return self.create_prod_with_secret_build(**kwargs) else: raise OsbsException("Unknown build type: '%s'" % build_type)
def test_orchestrate_build_failed_waiting(tmpdir, pod_available, pod_failure_reason, cancel_fails, expected): workflow = mock_workflow(tmpdir) mock_osbs() class MockPodResponse(object): def __init__(self, pod_failure_reason): self.pod_failure_reason = pod_failure_reason def get_failure_reason(self): if isinstance(self.pod_failure_reason, Exception): raise self.pod_failure_reason return self.pod_failure_reason def mock_wait_for_build_to_finish(build_name): if build_name == 'worker-build-ppc64le': raise OsbsException('it happens') return make_build_response(build_name, 'Failed') (flexmock(OSBS).should_receive('wait_for_build_to_finish').replace_with( mock_wait_for_build_to_finish)) cancel_build_expectation = flexmock(OSBS).should_receive('cancel_build') if cancel_fails: cancel_build_expectation.and_raise(OsbsException) cancel_build_expectation.once() expectation = flexmock(OSBS).should_receive('get_pod_for_build') if pod_available: expectation.and_return(MockPodResponse(pod_failure_reason)) else: expectation.and_raise(OsbsException()) mock_reactor_config(tmpdir) runner = BuildStepPluginsRunner( workflow.builder.tasker, workflow, [{ 'name': OrchestrateBuildPlugin.key, 'args': { 'platforms': ['x86_64', 'ppc64le'], 'build_kwargs': make_worker_build_kwargs(), 'osbs_client_config': str(tmpdir), } }]) build_result = runner.run() assert build_result.is_failed() annotations = build_result.annotations assert set(annotations['worker-builds'].keys()) == {'x86_64', 'ppc64le'} fail_reason = json.loads(build_result.fail_reason)['ppc64le'] if expected is KeyError: assert 'pod' not in fail_reason else: assert fail_reason['pod'] == expected
def get_build_config_by_labels(self, label_selectors): """ Returns a build config matching the given label selectors. This method will raise OsbsException if not exactly one build config is found. """ items = self.get_all_build_configs_by_labels(label_selectors) if not items: raise OsbsException("Build config not found for labels: %r" % (label_selectors, )) if len(items) > 1: raise OsbsException( "More than one build config found for labels: %r" % (label_selectors, )) return items[0]
def _select(self): sel = self.curl_multi.select(SELECT_TIMEOUT) if sel == -1: raise OsbsException("CurlMulti.select() timed out") elif sel == 0: # sel==0 means curl_multi_fdset returned -1 # manual page suggests sleeping >100ms when this happens:( time.sleep(0.1)
def clone_git_repo(git_url, target_dir=None, commit=None, retry_times=GIT_MAX_RETRIES, branch=None, depth=None): """ clone provided git repo to target_dir, optionally checkout provided commit :param git_url: str, git repo to clone :param target_dir: str, filesystem path where the repo should be cloned :param commit: str, commit to checkout, SHA-1 or ref :param retry_times: int, number of retries for git clone :param branch: str, optional branch of the commit, required if depth is provided :param depth: int, optional expected depth :return: str, int, commit ID of HEAD """ retry_delay = GIT_BACKOFF_FACTOR target_dir = target_dir or os.path.join(tempfile.mkdtemp(), "repo") commit = commit or "master" logger.info("cloning git repo '%s'", git_url) logger.debug("url = '%s', dir = '%s', commit = '%s'", git_url, target_dir, commit) cmd = ["git", "clone"] if branch: cmd += ["-b", branch, "--single-branch"] if depth: cmd += ["--depth", str(depth)] elif depth: logger.warning("branch not provided for %s, depth setting ignored", git_url) depth = None cmd += [git_url, target_dir] logger.debug("cloning '%s'", cmd) repo_commit = '' repo_depth = None for counter in range(retry_times + 1): try: # we are using check_output, even though we aren't using # the return value, but we will get 'output' in exception subprocess.check_output(cmd, stderr=subprocess.STDOUT) repo_commit, repo_depth = reset_git_repo(target_dir, commit, depth) break except subprocess.CalledProcessError as exc: if counter != retry_times: logger.info("retrying command '%s':\n '%s'", cmd, exc.output) time.sleep(retry_delay * (2**counter)) else: raise OsbsException("Unable to clone git repo '%s' " "branch '%s'" % (git_url, branch), cause=exc, traceback=sys.exc_info()[2]) return ClonedRepoData(target_dir, repo_commit, repo_depth)
def wait_for_build_to_get_scheduled(self, build_id): for _ in range(WAIT_RETRY): try: build_response = self.wait(build_id, BUILD_FINISHED_STATES + BUILD_RUNNING_STATES) return build_response except OsbsWatchBuildNotFound: continue raise OsbsException('Failed to schedule a build in {} attempts: {}'.format(WAIT_RETRY, build_id))
def _validate_container_config_file(self): try: container_config_path = os.path.join(self.dir_path, REPO_CONTAINER_CONFIG) self.container = read_yaml_from_file_path(container_config_path, 'schemas/container.json') or {} except Exception as e: msg = ('Failed to load or validate container file "{file}": {reason}' .format(file=container_config_path, reason=e)) raise OsbsException(msg)
def import_image(self, name): """ Import image tags from a Docker registry into an ImageStream :return: bool, whether new tags were imported """ # Get the JSON for the ImageStream url = self._build_url("imagestreams/%s" % name) imagestream_json = self._get(url).json() logger.debug("imagestream: %r", imagestream_json) spec = imagestream_json.get('spec', {}) if 'dockerImageRepository' not in spec: raise OsbsException('No dockerImageRepository for image import') # Note the tags before import oldtags = imagestream_json.get('status', {}).get('tags', []) logger.debug("tags before import: %r", oldtags) # Mark it as needing import imagestream_json['metadata'].setdefault('annotations', {}) check_annotation = "openshift.io/image.dockerRepositoryCheck" imagestream_json['metadata']['annotations'][check_annotation] = '' response = self._put(url, data=json.dumps(imagestream_json), use_json=True) check_response(response) # Watch for it to be updated resource_version = imagestream_json['metadata']['resourceVersion'] for changetype, obj in self.watch_resource( "imagestreams", name, resourceVersion=resource_version): logger.info("Change type: %r", changetype) if changetype == WATCH_DELETED: logger.info("Watched ImageStream was deleted") break if changetype == WATCH_ERROR: logger.error("Error watching ImageStream") break if changetype == WATCH_MODIFIED: logger.info("ImageStream modified") metadata = obj.get('metadata', {}) annotations = metadata.get('annotations', {}) logger.info("ImageStream annotations: %r", annotations) if annotations.get(check_annotation, False): logger.info("ImageStream updated") # Find out if there are new tags status = obj.get('status', {}) newtags = status.get('tags', []) logger.debug("tags after import: %r", newtags) return newtags != oldtags return False
def wait_for_build_to_finish(self, build_id): for retry in range(1, 10): try: build_response = self.wait(build_id, BUILD_FINISHED_STATES) return build_response except OsbsWatchBuildNotFound: # this is woraround for https://github.com/openshift/origin/issues/2348 logger.error("I'm going to wait again. Retry #%d.", retry) continue raise OsbsException("Failed to wait for a build: %s" % build_id)
def __init__(self, dir_path='', file_name=REPO_CONFIG_FILE, depth=None, git_uri=None, git_branch=None, git_ref=None): self._config_parser = ConfigParser() self.container = {} self.depth = depth or 0 self.autorebuild = {} # Keep track of the repo metadata in the repo configuration self.git_uri = git_uri self.git_branch = git_branch self.git_ref = git_ref # Set default options self._config_parser.readfp(StringIO(self.DEFAULT_CONFIG)) # pylint: disable=W1505; py2 config_path = os.path.join(dir_path, file_name) if os.path.exists(config_path): self._config_parser.read(config_path) file_path = os.path.join(dir_path, REPO_CONTAINER_CONFIG) if os.path.exists(file_path): try: self.container = read_yaml_from_file_path( file_path, 'schemas/container.json') or {} except Exception as e: msg = ( 'Failed to load or validate container file "{file}": {reason}' .format(file=file_path, reason=e)) raise OsbsException(msg) # container values may be set to None container_compose = self.container.get('compose') or {} modules = container_compose.get('modules') or [] self.autorebuild = self.container.get('autorebuild') or {} self.container_module_specs = [] value_errors = [] for module in modules: try: self.container_module_specs.append(ModuleSpec.from_str(module)) except ValueError as e: value_errors.append(e) if value_errors: raise ValueError(value_errors) flatpak = self.container.get('flatpak') or {} self.is_flatpak = bool(flatpak) self.flatpak_base_image = flatpak.get('base_image') self.flatpak_component = flatpak.get('component') self.flatpak_name = flatpak.get('name')
def get_build_config_by_labels(self, label_selectors): """ Returns a build config matching the given label selectors. This method will raise OsbsException if not exactly one build config is found. """ labels = ['%s=%s' % (field, value) for field, value in label_selectors] labels = ','.join(labels) url = self._build_url("buildconfigs/", labelSelector=labels) items = self._get(url).json()['items'] if not items: raise OsbsException("Build config not found for labels: %r" % (label_selectors, )) if len(items) > 1: raise OsbsException( "More than one build config found for labels: %r" % (label_selectors, )) return items[0]
def template(self): if self._template is None: path = os.path.join(self.build_json_store, DEFAULT_OUTER_TEMPLATE) logger.debug("loading template from path %s", path) try: with open(path, "r") as fp: self._template = json.load(fp) except (IOError, OSError) as ex: raise OsbsException("Can't open template '%s': %s" % (path, repr(ex))) return self._template
def template(self): if self._template is None: path = os.path.join(self.user_params.build_json_dir, self._outer_template_path) logger.debug("loading template from path %s", path) try: with open(path, "r") as fp: self._template = json.load(fp) except (IOError, OSError) as ex: raise OsbsException("Can't open template '%s': %s" % (path, repr(ex))) return self._template
def _verify_no_running_builds(self, build_config_name): running_builds = self._get_running_builds_for_build_config(build_config_name) rb_len = len(running_builds) if rb_len > 0: if rb_len == 1: rb = running_builds[0] msg = "Build %s for %s in state %s, can't proceed." % \ (rb.get_build_name(), build_config_name, rb.status) else: msg = self._panic_msg_for_more_running_builds(build_config_name, running_builds) raise OsbsException(msg)
def _create_build_config_and_build(self, build_request): # TODO: test this method more thoroughly build_json = build_request.render() api_version = build_json['apiVersion'] if api_version != self.os_conf.get_openshift_api_version(): raise OsbsValidationException("BuildConfig template has incorrect apiVersion (%s)" % api_version) build_config_name = build_json['metadata']['name'] # check if a build already exists for this config; if so then raise running_builds = self._get_running_builds_for_build_config(build_config_name) rb_len = len(running_builds) if rb_len > 0: if rb_len == 1: rb = running_builds[0] msg = 'Build %s for %s in state %s, can\'t proceed.' % \ (rb.get_build_name(), build_config_name, rb.status) else: msg = self._panic_msg_for_more_running_builds(build_config_name, running_builds) raise OsbsException(msg) try: # see if there's already a build config existing_bc = self.os.get_build_config(build_config_name) except OsbsException: # doesn't exist existing_bc = None build = None if existing_bc is not None: utils.buildconfig_update(existing_bc, build_json) logger.debug('build config for %s already exists, updating...', build_config_name) self.os.update_build_config(build_config_name, json.dumps(existing_bc)) else: # if it doesn't exist, then create it logger.debug('build config for %s doesn\'t exist, creating...', build_config_name) bc = self.os.create_build_config(json.dumps(build_json)).json() # if there's an "ImageChangeTrigger" on the BuildConfig and "From" is of type # "ImageStreamTag", the build will be scheduled automatically # see https://github.com/projectatomic/osbs-client/issues/205 if build_request.is_auto_instantiated(): prev_version = bc['status']['lastVersion'] build_id = self.os.wait_for_new_build_config_instance(build_config_name, prev_version) build = BuildResponse(self.os.get_build(build_id).json()) if build is None: response = self.os.start_build(build_config_name) build = BuildResponse(response.json()) return build
def start_pipeline_run(self): if not self.input_data: raise OsbsException( "No input data provided for pipeline run to start") run_name = self.input_data.get('metadata', {}).get('name') if run_name != self.pipeline_run_name: msg = f"Pipeline run name provided '{self.pipeline_run_name}' is different " \ f"than in input data '{run_name}'" raise OsbsException(msg) url = self.os.build_url(self.api_path, self.api_version, "pipelineruns") response = self.os.post( url, data=json.dumps(self.input_data), headers={ "Content-Type": "application/json", "Accept": "application/json" }, ) return response.json()
def catch_exceptions(*args, **kwargs): # XXX: remove this in the future if kwargs.pop("namespace", None): warnings.warn("OSBS.%s: the 'namespace' argument is no longer supported" % func.__name__) try: return func(*args, **kwargs) except OsbsException: # Re-raise OsbsExceptions raise except Exception as ex: # Convert anything else to OsbsException # Python 3 has implicit exception chaining and enhanced # reporting, so you get the original traceback as well as # the one originating here. # For Python 2, let's do that explicitly. raise OsbsException(cause=ex, traceback=sys.exc_info()[2])
def kerberos_ccache_init(principal, keytab_file, ccache_file=None): """ Checks whether kerberos credential cache has ticket-granting ticket that is valid for at least an hour. Default ccache is used unless ccache_file is provided. In that case, KRB5CCNAME environment variable is set to the value of ccache_file if we successfully obtain the ticket. """ tgt_valid = False env = {"LC_ALL": "C"} # klist uses locales to format date on RHEL7+ if ccache_file: env["KRB5CCNAME"] = ccache_file # check if we have tgt that is valid more than one hour rc, klist, _ = run(["klist"], extraenv=env) if rc == 0: for line in klist.splitlines(): m = re.match(KLIST_TGT_RE, line) if m: year = m.group("year") if len(year) == 2: year = "20" + year expires = datetime.datetime(int(year), int(m.group("month")), int(m.group("day")), int(m.group("hour")), int(m.group("minute")), int(m.group("second"))) if expires - datetime.datetime.now() > datetime.timedelta( hours=1): logger.debug("Valid TGT found, not renewing") tgt_valid = True break if not tgt_valid: logger.debug("Retrieving kerberos TGT") rc, out, err = run(["kinit", "-k", "-t", keytab_file, principal], extraenv=env) if rc != 0: raise OsbsException("kinit returned %s:\nstdout: %s\nstderr: %s" % (rc, out, err)) if ccache_file: os.environ["KRB5CCNAME"] = ccache_file
def run_command(*popenargs, **kwargs): """ Run command with arguments and return its output as a byte string. This is originally taken from subprocess.py of python 2.7 """ process = subprocess.Popen(stdout=subprocess.PIPE, stderr=subprocess.STDOUT, *popenargs, **kwargs) output, _ = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] raise OsbsException( message="Command %s returned %s\n\n%s" % (cmd, retcode, output) ) return output
def request(self, url, *args, **kwargs): try: stream = HttpStream(url, *args, verbose=self.verbose, **kwargs) if kwargs.get('stream', False): return stream with stream as s: content = s.req.content return HttpResponse(s.status_code, s.headers, content) # Timeout will catch both ConnectTimout and ReadTimeout except (RetryError, Timeout) as ex: raise OsbsNetworkException(url, str(ex), '', cause=ex, traceback=sys.exc_info()[2]) except HTTPError as ex: raise OsbsNetworkException(url, str(ex), ex.response.status_code, cause=ex, traceback=sys.exc_info()[2]) except Exception as ex: raise OsbsException(cause=ex, traceback=sys.exc_info()[2])