def _fetch_by_bug(self, bug_id): # print("fetch_by_bug") try: url = "/bugs/%i/advisories.json" % bug_id rj = self._get(url) stored = False for e in rj: if not stored: stored = True self._fetch(e['id']) else: print( 'Warning: Ignoring additional erratum ' + str(e['id']) + ' for bug ', str(bug_id)) except RuntimeError: # Requests seems to loop infinitely if this happens... raise ErrataException('Pigeon crap. Did it forget to run kinit?') except IndexError: # errata_id not found raise ErrataException('Errata ID field not found in response') except LookupError: # Errata not found pass except Exception: # Todo: better handling raise
def _processResponse(self, r): if r.status_code in [200, 201, 202, 203, 204]: return # all good # If subclassed as an Erratum and we have an ID, add it # to the error message err_msg = '' try: if type(self.errata_id) is int and self.errata_id > 0: err_msg += 'Erratum ' + str(self.errata_id) + ': ' except AttributeError: pass # Generate a really big message if e.g. bug is in a different # erratum if r.status_code in [400, 422]: rj = r.json() if rj is None: raise Exception(err_msg + 'No Json returned') if 'error' in rj: # err_msg += '; '.join(rj['error']) err_msg += str(rj['error']) else: # TODO: drop jsonpath pe = parse('errors[*]') for match in pe.find(rj): # This grabs the index since the json returns a dict for k in match.value: err_msg += k + ": " petmp = parse('errors.' + k + '[*]') for m in petmp.find(rj): if isinstance(m.value, six.string_types): err_msg += m.value + "\n" elif type(m.value) is int: err_msg += str(m.value) + "\n" else: for n in m.value: err_msg += str(n) + "\n" raise ErrataException(err_msg) if r.status_code in [401]: # lhh - this is not a typo, and the syntax is correct, # I assure you. raise ErrataException('Pigeon crap. Did it forget to run kinit?') if r.status_code in [500]: err_msg += "Broke errata tool!" print(r.json()) raise ErrataException(err_msg) if r.status_code in [404]: err_msg += 'Bug in your code - wrong method for this api? ' err_msg += 'Wrong location?' print(r.json()) raise ErrataException(err_msg) raise ErrataException(err_msg + "Unhandled HTTP status code: " + str(r.status_code))
def _get(self, u, **kwargs): """_get is convience method that retrives content from server Recognized kwargs 'data' for requests data object to send with get call 'json' for requests json object to send with get call 'raw' bool (defaulting to False) for returning response object by default the return value is the response.json() object from Requests """ self._set_username() url = self.canonical_url(u) ret_data = None ret_json = None start = time.time() return_json_decoded_data = True if kwargs is not None: if 'data' in kwargs: ret_data = requests.get(url, auth=self._auth, data=kwargs['data'], verify=self.ssl_verify) elif 'json' in kwargs: ret_data = requests.get(url, auth=self._auth, json=kwargs['json'], verify=self.ssl_verify) if 'raw' in kwargs: return_json_decoded_data = not kwargs['raw'] if ret_data is None: ret_data = requests.get(url, auth=self._auth, verify=self.ssl_verify) self._record('GET', url, time.time() - start) if ret_json is None and ret_data is not None: if ret_data.status_code == 200: if return_json_decoded_data: ret_json = ret_data.json() else: return ret_data elif ret_data.status_code in [401]: raise ErrataException( 'Pigeon crap. Did it forget to run kinit?') elif ret_data.status_code in [403]: raise ErrataException( 'You need Errata access for this operation!') else: print("Result not handled: " + str(ret_data.text)) print("While fetching: " + url) raise ErrataException(str(ret_data)) return ret_json
def addBuilds(self, buildlist, **kwargs): if self._new: raise ErrataException('Cannot add builds to unfiled erratum') if 'release' not in kwargs and self._release is None: if len(self.errata_builds) != 1: raise ErrataException('Need to specify a release') return self.addBuildsDirect(buildlist, self.errata_builds.keys()[0], **kwargs) if 'release' in kwargs: release = kwargs['release'] del kwargs['release'] else: release = self._release return self.addBuildsDirect(buildlist, release, **kwargs)
def addBuilds(self, buildlist, **kwargs): if self._new: raise ErrataException('Cannot add builds to unfiled erratum') release = None if 'release' in kwargs: release = kwargs['release'] del kwargs['release'] if release is None and len(self.errata_builds.keys()) == 1: release = list(self.errata_builds.keys())[0] if release is None: raise ErrataException('Need to specify a release') return self.addBuildsDirect(buildlist, release, **kwargs)
def _get(self, u, **kwargs): self._set_username() url = self.canonical_url(u) ret_data = None ret_json = None start = time.time() if kwargs is not None: if 'data' in kwargs: ret_data = requests.get(url, auth=self._auth, data=kwargs['data'], verify=self.ssl_verify) elif 'json' in kwargs: ret_data = requests.get(url, auth=self._auth, json=kwargs['json'], verify=self.ssl_verify) if ret_data is None: ret_data = requests.get(url, auth=self._auth, verify=self.ssl_verify) self._record('GET', url, time.time() - start) if ret_json is None and ret_data is not None: if ret_data.status_code == 200: ret_json = ret_data.json() elif ret_data.status_code in [401]: raise ErrataException( 'Pigeon crap. Did it forget to run kinit?') elif ret_data.status_code in [403]: raise ErrataException( 'You need Errata access for this operation!') elif ret_data.status_code in [500]: raise LookupError('No matching errata') else: print("Result not handled: " + str(ret_data)) print("While fetching: " + url) raise ErrataException(str(ret_data)) return ret_json
def __init__(self, **kwargs): self.ssl_verify = security.security_settings.ssl_verify() # Blank erratum e.g. if create is required self._do_init() if 'errata_id' in kwargs: self._fetch(kwargs['errata_id']) return if 'bug_id' in kwargs: self._fetch_by_bug(kwargs['bug_id']) return if 'product' not in kwargs: raise ErrataException('Creating errata requires a product') if 'release' not in kwargs: raise ErrataException('Creating errata requires a release') if 'format' in kwargs: self._format = kwargs['format'] self._new = True self.errata_name = '(unassigned)' self.errata_state = 'NEW_FILES' self._product = kwargs['product'] self._release = kwargs['release'] self.update(**kwargs) if 'solution' not in kwargs: self.solution = self.fmt("Before applying this update, \ make sure all previously released errata relevant to your system \ have been applied.\n\ \n\ For details on how to apply this update, refer to:\n\ \n\ https://access.redhat.com/articles/11258") # errata tool defaults if 'errata_type' in kwargs: self.errata_type = kwargs['errata_type'] else: self.errata_type = 'RHBA'
def setState(self, state): if self._new: raise ErrataException('Cannot simultaneously create and change ' + 'an erratum\'s state') if self.errata_id == 0: raise ErrataException('Cannot change state for uninitialized ' + 'erratum') if self.errata_state.upper() == 'NEW_FILES': if state.upper() == 'QE': self.errata_state = 'QE' elif self.errata_state.upper() == 'QE': if state.upper() == 'NEW_FILES': self.errata_state = 'NEW_FILES' if state.upper() == 'REL_PREP': self.errata_state = 'REL_PREP' elif self.errata_state.upper() == 'REL_PREP': if state.upper() == 'NEW_FILES': self.errata_state = 'NEW_FILES' if state.upper() == 'QE': self.errata_state = 'QE' else: raise ErrataException('Cannot change state from ' + self.errata_state.upper() + " to " + state.upper())
def _set_username(self, **kwargs): if self._username is not None: return try: (ret, ctx) = kerberos.authGSSClientInit('*****@*****.**') assert (ret == kerberos.AUTH_GSS_COMPLETE) ret = kerberos.authGSSClientInquireCred(ctx) assert (ret == kerberos.AUTH_GSS_COMPLETE) # XXX What if you have >1 ticket? ret = kerberos.authGSSClientUserName(ctx) if '@' in ret: self._username = ret.split('@')[0] else: self._username = ret except AssertionError: raise ErrataException('Pigeon crap. Did it forget to run kinit?')
def fetch_releases(self): page = 1 releases = {} while True: ret = self._get('/api/v1/releases?page[number]=' + str(page)) if ret is None: break if 'data' not in ret: raise ErrataException('Malformed response from server') if len(ret['data']) == 0: break for r in ret['data']: attrs = r['attributes'] if r['type'] != 'releases': continue info = {} info['id'] = int(r['id']) info['name'] = attrs['name'] info['description'] = attrs['description'] info['async'] = attrs['is_async'] info['brew_tags'] = {} for t in r['relationships']['brew_tags']: info['brew_tags'][int(t['id'])] = t['name'] if attrs['enabled']: info['enabled'] = True else: info['enabled'] = False info['bz_flags'] = [] for f in attrs['blocker_flags']: if f not in ('devel_ack', 'pm_ack', 'qa_ack'): info['bz_flags'].append(f) info['versions'] = {} info['products'] = {} if 'product_versions' in r['relationships']: for t in r['relationships']['product_versions']: info['versions'][int(t['id'])] = t['name'] releases[int(r['id'])] = info page = page + 1 self.releases = releases
def commit(self): if self.retry_times <= self.none_throw_threshold: self.retry_times = self.retry_times + 1 raise ErrataException("this is an exception from testErratum") else: pass
def _write(self): pdata = {} # See below for APIs used when talking to the errata tool. if self._new: if self.package_owner_email is None: raise ErrataException("Can't create erratum without " + "package owner email") if self.manager_email is None: if self.manager_id: manager = User(self.manager_id) self.manager_email = manager.email_address else: raise ErrataException("Can't create erratum without " + "manager email or manager id") if self._product is None: raise ErrataException("Can't create erratum with no " + "product specified") if self._release is None: raise ErrataException("Can't create erratum with no " + "release specified") if self.errata_type is None: self.errata_type = 'RHBA' if self.errata_type == 'RHSA': val = 'None' if self.security_impact is not None: val = self.security_impact pdata['advisory[security_impact]'] = val pdata['product'] = self._product pdata['release'] = self._release pdata['advisory[package_owner_email]'] = self.package_owner_email pdata['advisory[manager_email]'] = self.manager_email if self.qe_email is not None and self.qe_email != '': pdata['advisory[assigned_to_email]'] = self.qe_email if self.qe_group is not None and self.qe_group != '': pdata['advisory[quality_responsibility_name]'] = self.qe_group if self.synopsis is None: raise ErrataException("Can't write erratum without synopsis") if self.topic is None: raise ErrataException("Can't write erratum without topic") if self.description is None: raise ErrataException("Can't write erratum without description") if self.solution is None: raise ErrataException("Can't write erratum without a solution") if self.errata_bugs is None: raise ErrataException("Can't write erratum without a list of " + "bugs") # Default from errata tool pdata['advisory[errata_type]'] = self.errata_type # POST/PUT a 1 or 0 value for this text_only boolean pdata['advisory[text_only]'] = int(self.text_only) if self.text_only_cpe: pdata['advisory[text_only_cpe]'] = self.text_only_cpe if self.publish_date_override: pdata['advisory[publish_date_override]'] = \ self.publish_date_override # ET automagically handles the severity for the synopsis in RHSA's # but will still see it as a docs change if we write the same one # back again, so remove it. if self.errata_type == 'RHSA': severity = r'^(Low|Moderate|Important|Critical): ' self.synopsis = re.sub(severity, "", self.synopsis) pdata['advisory[cve]'] = self.cve_names pdata['advisory[synopsis]'] = self.synopsis pdata['advisory[topic]'] = self.topic pdata['advisory[description]'] = self.description pdata['advisory[solution]'] = self.solution # XXX Delete all bugs is a special case last_bug = None if len(self.errata_bugs) == 0 and len(self._original_bugs) > 0: last_bug = self._original_bugs[0] self.errata_bugs = [last_bug] # Add back any Vulnerability bugs allbugs = list(set(self.errata_bugs) | set(self._cve_bugs)) idsfixed = ' '.join(str(i) for i in allbugs) pdata['advisory[idsfixed]'] = idsfixed # Sync newly added bug states newbugs = list(set(allbugs) - set(self._original_bugs)) if len(newbugs): # url = '/api/v1/bug/refresh' # print(allbugs) # r = self._post(url, data=newbugs) # self._processResponse(r) # ^ XXX broken # # XXX Sync bug states by force using UI # Note: UI limits syncs to 100 bugs per run, so split # up into chunks syncs = [newbugs[x:x + 100] for x in range(0, len(newbugs), 100)] bug_list = {} for s in syncs: bug_list['issue_list'] = ' '.join(str(i) for i in s) url = "/bugs/sync_bug_list" r = self._post(url, data=bug_list) # XXX should we process return code? # Push it if self._new: # REFERENCE # New is 'POST' url = "/api/v1/erratum" r = self._post(url, data=pdata) self._processResponse(r) rj = r.json() # This might need a "pdc_" prefix, rhbz 1493773 for background json_errata_type = self.errata_type.lower() try: rj['errata'][json_errata_type] except KeyError: json_errata_type = 'pdc_%s' % json_errata_type rj['errata'][json_errata_type] self.errata_id = rj['errata'][json_errata_type]['errata_id'] # XXX return JSON returns full advisory name but not # typical advisory name - e.g. RHSA-2015:19999-01, but not # RHSA-2015:19999, but it's close enough self.errata_name = rj['errata'][json_errata_type]['fulladvisory'] else: # REFERENCE # Update is 'PUT' url = "/api/v1/erratum/%i" % self.errata_id r = self._put(url, data=pdata) self._processResponse(r) # XXX WOW VERY HACK # If deleting last bug... if last_bug is not None: # This doesn't work to remove the last bug, nor does setting # idsfixed to empty-string # url = "/api/v1/erratum/%i/remove_bug" % self.errata_id # pdata = {'bug': str(last_bug)} # Solution: Use hacks to pretend we're using the remove-bugs # web UI :( url = '/bugs/remove_bugs_from_errata/%i' % self.errata_id pdata = {} pdata['bug[' + str(last_bug) + ']'] = 1 # Handle weird interaction we get in this particular case try: r = self._post(url, data=pdata) except requests_kerberos.exceptions.MutualAuthenticationError: pass self._processResponse(r)
def _fetch(self, errata_id): self._new = False self._update = False self._buildschanged = False self.errata_builds = {} self.current_flags = [] try: # TODO: remove call to /advisory/X.json once new API # supports all the information endpoint_list = [ '/advisory/' + str(errata_id) + '.json', '/api/v1/erratum/' + str(errata_id), ] # Want to ditch advisory_old eventually advisory_old = None advisory = None erratum = None for endpoint in endpoint_list: r = self._get(endpoint) if r is None: continue if advisory is None and 'erratum' in endpoint: advisory = r continue # Fallthrough if advisory_old is None: advisory_old = r if advisory is None: print('do not have requested data bailing') return None # Short circuit to get the advisory for key in advisory['errata']: erratum = advisory['errata'][key] self.errata_type = key.upper() # We cannot PUT a "PDC_"-prefixed value back to the server. # The server expects "RHBA", not "PDC_RHBA". We will just # remove the type internally here. # (See bz 1493773 for background on why the key has the pdc_ # prefix here.) if self.errata_type.startswith('PDC_'): self.errata_type = self.errata_type[4:] break self.errata_id = erratum['id'] # NEW_FILES QE etc. self.errata_state = erratum['status'] self._original_state = self.errata_state # Check if the erratum is under embargo self.embargoed = False self.release_date = erratum['release_date'] if self.release_date is not None: cur = datetime.datetime.utcnow() cur = str(cur).split()[0] if self.release_date > cur: self.embargoed = True # Target Ship date d = erratum['publish_date_override'] if d is not None: pd = time.strptime(str(d), '%Y-%m-%dT%H:%M:%SZ') self.publish_date_override = time.strftime('%Y-%b-%d', pd) # Actual ship date (if in SHIPPED_LIVE) if self.errata_state in ('SHIPPED_LIVE'): d = erratum['actual_ship_date'] d = time.strptime(str(d), '%Y-%m-%dT%H:%M:%SZ') self.ship_date = time.strftime('%Y-%b-%d', d) # File date d = erratum['created_at'] d = time.strptime(str(d), '%Y-%m-%dT%H:%M:%SZ') self.creation_date = time.strftime('%Y-%b-%d', d) d = time.strftime('%Y-%b-%d', time.gmtime()) if self.ship_date is not None: d = self.ship_date filed = datetime.datetime.strptime(self.creation_date, '%Y-%b-%d') ship = datetime.datetime.strptime(d, '%Y-%b-%d') age = ship - filed self.age = age.days # Baseline flags. if self.errata_state in ('QE'): if 'sign_requested' in erratum and \ erratum['sign_requested'] == 0: self.addFlags('request_sigs') if 'rhnqa' in erratum and erratum['rhnqa'] == 0: self.addFlags('needs_distqa') if 'doc_complete' in erratum and erratum['doc_complete'] == 0: self.addFlags('needs_docs') if self.errata_state == 'NEW_FILES': self.addFlags('needs_devel') # Note: new errata return values will have other bits. self.errata_name = erratum['fulladvisory'] # Grab immutable fields self._product = advisory_old['product']['short_name'] self._release = advisory_old['release']['name'] # A maybe-empty list, containing eg. "rpm" or "docker" self.content_types = erratum['content_types'] # store product and release IDs self.product_id = advisory_old['product']['id'] self.release_id = advisory_old['release']['id'] self.package_owner_email = advisory_old['people']['package_owner'] self.reporter = advisory_old['people']['reporter'] self.qe_email = advisory_old['people']['assigned_to'] self.qe_group = advisory_old['people']['qe_group'] # XXX Errata tool doesn't report manager? # https://bugzilla.redhat.com/show_bug.cgi?id=1664884 # self.manager_email = ??? self.manager_id = erratum.get('manager_id') # Grab mutable errata content self.text_only = erratum['text_only'] self.synopsis = erratum['synopsis'] content = advisory['content']['content'] self.text_only_cpe = content['text_only_cpe'] self.topic = content['topic'] self.description = content['description'] self.solution = content['solution'] self.errata_bugs = [ int(b['bug']['id']) for b in advisory['bugs']['bugs'] ] self.cve_names = content['cve'] if self.cve_names == '': self.cve_names = None self._original_bugs = list(self.errata_bugs) self._cache_bug_info(self._original_bugs) # Try to check to see if we need devel assistance, qe assistance or # rel prep assistance if self.errata_state == 'QE': self._check_tps() self._check_bugs() self._check_need_rel_prep() # Check for security review if 'rhsa' in advisory['errata']: sa = advisory['errata']['rhsa']['security_approved'] self.security_impact = advisory['errata']['rhsa'][ 'security_impact'] # NOQA if sa is None: self.addFlags('request_security') elif sa is False: self.addFlags('needs_security') check_signatures = self.errata_state != 'NEW_FILES' self._get_build_list(check_signatures) self.batch_id = erratum.get('batch_id') return except RuntimeError: # Requests seems to loop infinitely if this happens... raise ErrataException('Pigeon crap. Did it forget to run kinit?') except IndexError: # errata_id not found raise ErrataException('Errata ID field not found in response') except Exception: # Todo: better handling raise