def create_jira(self, form): # Get authentication info for the JIRA login auth = self.get_credentials(form['jira_user'].value(), form['jira_password'].value()) # Create the JIRA ticket jira = JIRA('https://www.bcgsc.ca/jira/', basic_auth=auth) library = form['library'].value() library = get_object_or_404(DlpLibrary, id=library) title = "Analysis of " + str(library) issue_dict = { 'project': { 'id': 11220 }, 'summary': title, 'issuetype': { 'name': 'Sub-task' }, 'priority': { 'name': 'Medium' }, 'assignee': { 'name': 'sochan' }, 'parent': { 'id': str(library.jira_ticket) }, } new_issue = jira.create_issue(fields=issue_dict) # Add Emma as watcher jira.add_watcher(new_issue.id, 'elaks') return str(new_issue)
def add_watchers(username, password, issue, watchers): ''' Given a list of watchers, add them to the provided JIRA Issue ''' Jira = JIRA(JIRA_URL, basic_auth=(username, password), max_retries=0) try: jira_issue = Jira.issue(issue) except JIRAError as e: raise JIRAError() for watcher in watchers: Jira.add_watcher(jira_issue, watcher)
class JIRA_Wapper(): def __init__(self, user, password, server_url='http://jira.calix.local', **kwargs): self.jira = JIRA(basic_auth=(user, password), server=server_url, **kwargs) def _list_projects(self): return sorted([pro.key for pro in self.jira.projects()]) def _get_a_issue(self, issue_id): return self.jira.issue(issue_id) def search_issues_by_jql(self, jql, **kwargs): return self.jira.search_issues(jql, **kwargs) def get_assignee(self, issue_id): return self._get_a_issue( issue_id).raw['fields']['assignee']['displayName'] def get_issue_link(self, issue_id): return self._get_a_issue( issue_id).raw['fields']['votes']['self'].replace( 'rest/api/2/issue', 'browse').replace('/votes', '') def get_issue_title(self, issue_id): return self._get_a_issue(issue_id).raw['fields']['summary'] def get_reporter(self, issue_id): return self._get_a_issue( issue_id).raw['fields']['reporter']['displayName'] def get_issue_type(self, issue_id): return self._get_a_issue(issue_id).raw['fields']['issuetype']['name'] def get_priority(self, issue_id): return self._get_a_issue(issue_id).raw['fields']['priority']['name'] def add_comment(self, issue_id, comment): self.jira.add_comment(issue_id, comment) def update_issue(self, issue_id, **kwargs): self._get_a_issue(issue_id).update(**kwargs) def delete_issue(self, issue): issue.delete() def add_watcher(self, issue_id, watcher): self.jira.add_watcher(issue_id, watcher)
def createJIRATicket(desc, jiraConnector): options = { 'server': jiraConnector['url'], 'verify': jiraConnector['verify'] } keyValue = pwdCaller('officeLdap')['data'] jira = JIRA(options, basic_auth=(keyValue['user'], keyValue['password'])) qType = 'Daily Audit' new_issue = jira.create_issue(project=jiraConnector['project'], summary=qType, description=desc, issuetype={'name': 'Task'}) jira.add_watcher(new_issue.id, jiraConnector['watcher']) return
def create_analysis_jira_ticket(library_id): ''' Create analysis jira ticket as subtask of library jira ticket Args: info (dict): Keys: library_id Returns: analysis_jira_ticket: jira ticket id (ex. SC-1234) ''' JIRA_USER = os.environ['JIRA_USERNAME'] JIRA_PASSWORD = os.environ['JIRA_PASSWORD'] jira_api = JIRA('https://www.bcgsc.ca/jira/', basic_auth=(JIRA_USER, JIRA_PASSWORD)) library = colossus_api.get('library', pool_id=library_id) sample_id = library['sample']['sample_id'] library_jira_ticket = library['jira_ticket'] issue = jira_api.issue(library_jira_ticket) log.info('Creating analysis JIRA ticket as sub task for {}'.format( library_jira_ticket)) # In order to search for library on Jira, # Jira ticket must include spaces sub_task = { 'project': { 'key': 'SC' }, 'summary': 'Analysis of {} - {}'.format(sample_id, library_id), 'issuetype': { 'name': 'Sub-task' }, 'parent': { 'id': issue.key } } sub_task_issue = jira_api.create_issue(fields=sub_task) analysis_jira_ticket = sub_task_issue.key # Add watchers jira_api.add_watcher(analysis_jira_ticket, JIRA_USER) jira_api.add_watcher(analysis_jira_ticket, 'jedwards') jira_api.add_watcher(analysis_jira_ticket, 'jbiele') jira_api.add_watcher(analysis_jira_ticket, 'jbwang') jira_api.add_watcher(analysis_jira_ticket, 'elaks') # Assign task to myself analysis_issue = jira_api.issue(analysis_jira_ticket) analysis_issue.update(assignee={'name': JIRA_USER}) log.info('Created analysis ticket {} for library {}'.format( analysis_jira_ticket, library_id)) return analysis_jira_ticket
def create_analysis_jira_ticket(library_id, sample, library_ticket): ''' Create analysis jira ticket as subtask of library jira ticket Args: info (dict): Keys: library_id Returns: analysis_jira_ticket: jira ticket id (ex. SC-1234) ''' JIRA_USER = os.environ['JIRA_USERNAME'] JIRA_PASSWORD = os.environ['JIRA_PASSWORD'] jira_api = JIRA('https://www.bcgsc.ca/jira/', basic_auth=(JIRA_USER, JIRA_PASSWORD)) issue = jira_api.issue(library_ticket) # In order to search for library on Jira, # Jira ticket must include spaces sub_task = { 'project': { 'key': 'SC' }, 'summary': '{} - {} TenX Analysis'.format(sample, library_id), 'issuetype': { 'name': 'Sub-task' }, 'parent': { 'id': issue.key } } sub_task_issue = jira_api.create_issue(fields=sub_task) analysis_jira_ticket = sub_task_issue.key # Add watchers jira_api.add_watcher(analysis_jira_ticket, JIRA_USER) # Assign task to myself analysis_issue = jira_api.issue(analysis_jira_ticket) analysis_issue.update(assignee={'name': JIRA_USER}) logging.info('Created analysis ticket {} for library {}'.format( analysis_jira_ticket, library_id)) return analysis_jira_ticket
class JiraTicket(Ticket): """Ticket driver for JIRA Supports adding list of watchers to maintenance issues created, custom finishing transition for when calling close, and custom issue types. Priorities will be mapped according to the impact status of the maintenance. A preferred mapping can be provided otherwise it defaults to using the Vanilla JIRA install names, eg: >>> { 'NO-IMPACT': {'name': 'Low'}, 'REDUCED-REDUNDANCY': {'name': 'Medium'}, 'DEGRADED': {'name': 'High'}, 'OUTAGE': {'name': 'Highest'}, } Example: >>> type(event) xmaintnote.event.XMaintNoteEvent >>> tkt = JiraTicket( event, url='http://localhost', username='******', password='******', watchers='noc', ) >>> tkt.exists() False >>> tkt.create() True >>> tkt.exists() True >>> tkt.ticket <JIRA Issue: key=u'MAINT-14', id=u'10013'> >>> tkt.impact vText('NO-IMPACT') >>> tkt.ticket.fields.priority <JIRA Priority: name=u'Low', id=u'4'> >>> tkt.ticket.fields.labels [u'example.com:137.035999173:WorkOrder-31415'] """ def _post_init( self, url='http://localhost:8080', username=None, password=None, project='MAINT', issuetype='Task', finished_transition='Done', watchers=None, pri_mapping=None, ): """Setup to initialize Jira client and any required settings If username or password aren't provided, will attempt to do actions as anonymous Args: url (str): URL to jira server. MUST have the URL scheme (http://) username (str): Username (if applicable) password (str): Password (if applicable) project (str): JIRA project handle issuetype (str): Issue type to file these issues as watchers (list): List of usernames to add as watchers to the maints finished_transition (str): Transition to move the issue into when calling the ``.close`` method. Default: Done pri_mapping (str): Map of maintenance impact name to JIRA priority dict. eg, {'NO-IMPACT': {'name': 'Low'}} """ # If either part of the credential tuple is unprovided, default to # anonymous credentials = (username, password) if not all(credentials): basic_auth = None else: basic_auth = credentials if not watchers: watchers = [] if not pri_mapping: pri_mapping = { 'NO-IMPACT': {'name': 'Low'}, 'REDUCED-REDUNDANCY': {'name': 'Medium'}, 'DEGRADED': {'name': 'High'}, 'OUTAGE': {'name': 'Highest'}, } self.jira = JIRA(url, basic_auth=basic_auth) self.project = project self.issuetype = issuetype self.finished_transition = finished_transition self.watchers = watchers self.pri_mapping = pri_mapping def exists(self): """Return bool for whether maintenance issue exists for this event Improvements: Currently not handling the case where multiple issues are returned which may hint that the key used isn't unique enough or people have manually added the same label to other things. Also no exception handling mostly because the exception return by JIRA is pretty descriptive Returns: exists (bool) """ existing = self.jira.search_issues('labels = {}'.format(self.key)) if existing: self.ticket = existing[0] return True if existing else False def create(self): """Create issue for event Pre-check factors such as chehcking if this is a duplicate. If so, stop further actions. Returns: success (bool) """ jira = self.jira # If issue doesn't exist, create it. Else return False for inability # Add watchers to the new ticket if not self.exists(): options = { 'project': self.project, 'summary': self.title, 'labels': [self.key], 'description': self.body, 'issuetype': {'name': self.issuetype}, 'priority': self.pri_mapping[self.impact], } new_issue = jira.create_issue(fields=options) self.ticket = new_issue [self._add_watcher(new_issue, w) for w in self.watchers] return True else: return False def close(self): """Return bool representing success or failure for closing issue If issue doesn't exist, will return False because it can't close. Returns: success (bool) """ jira = self.jira finished_transition = self.finished_transition if self.exists(): # Fetch the transitions that we can put the current issue into. # Search through these for the provided ``finished_transition`` # from init. If not found, raise error. tkt = self.ticket transitions = jira.transitions(tkt) transition_ids = [ t['id'] for t in transitions if t['name'] == self.finished_transition ] if not transition_ids: raise ValueError( 'Transition "{}" not found'.format(finished_transition) ) t = transition_ids[0] jira.transition_issue(tkt, t) else: return False def _add_watcher(self, issue, watcher): """Add watcher to issue""" self.jira.add_watcher(issue, watcher)
class JiraTicket(Ticket): """Ticket driver for JIRA Supports adding list of watchers to maintenance issues created, custom finishing transition for when calling close, and custom issue types. Priorities will be mapped according to the impact status of the maintenance. A preferred mapping can be provided otherwise it defaults to using the Vanilla JIRA install names, eg: >>> { 'NO-IMPACT': {'name': 'Low'}, 'REDUCED-REDUNDANCY': {'name': 'Medium'}, 'DEGRADED': {'name': 'High'}, 'OUTAGE': {'name': 'Highest'}, } Example: >>> type(event) xmaintnote.event.XMaintNoteEvent >>> tkt = JiraTicket( event, url='http://localhost', username='******', password='******', watchers='noc', ) >>> tkt.exists() False >>> tkt.create() True >>> tkt.exists() True >>> tkt.ticket <JIRA Issue: key=u'MAINT-14', id=u'10013'> >>> tkt.impact vText('NO-IMPACT') >>> tkt.ticket.fields.priority <JIRA Priority: name=u'Low', id=u'4'> >>> tkt.ticket.fields.labels [u'example.com:137.035999173:WorkOrder-31415'] """ def _post_init( self, url='http://localhost:8080', username=None, password=None, project='MAINT', issuetype='Task', finished_transition='Done', watchers=None, pri_mapping=None, ): """Setup to initialize Jira client and any required settings If username or password aren't provided, will attempt to do actions as anonymous Args: url (str): URL to jira server. MUST have the URL scheme (http://) username (str): Username (if applicable) password (str): Password (if applicable) project (str): JIRA project handle issuetype (str): Issue type to file these issues as watchers (list): List of usernames to add as watchers to the maints finished_transition (str): Transition to move the issue into when calling the ``.close`` method. Default: Done pri_mapping (str): Map of maintenance impact name to JIRA priority dict. eg, {'NO-IMPACT': {'name': 'Low'}} """ # If either part of the credential tuple is unprovided, default to # anonymous credentials = (username, password) if not all(credentials): basic_auth = None else: basic_auth = credentials if not watchers: watchers = [] if not pri_mapping: pri_mapping = { 'NO-IMPACT': { 'name': 'Low' }, 'REDUCED-REDUNDANCY': { 'name': 'Medium' }, 'DEGRADED': { 'name': 'High' }, 'OUTAGE': { 'name': 'Highest' }, } self.jira = JIRA(url, basic_auth=basic_auth) self.project = project self.issuetype = issuetype self.finished_transition = finished_transition self.watchers = watchers self.pri_mapping = pri_mapping def exists(self): """Return bool for whether maintenance issue exists for this event Improvements: Currently not handling the case where multiple issues are returned which may hint that the key used isn't unique enough or people have manually added the same label to other things. Also no exception handling mostly because the exception return by JIRA is pretty descriptive Returns: exists (bool) """ existing = self.jira.search_issues('labels = {}'.format(self.key)) if existing: self.ticket = existing[0] return True if existing else False def create(self): """Create issue for event Pre-check factors such as chehcking if this is a duplicate. If so, stop further actions. Returns: success (bool) """ jira = self.jira # If issue doesn't exist, create it. Else return False for inability # Add watchers to the new ticket if not self.exists(): options = { 'project': self.project, 'summary': self.title, 'labels': [self.key], 'description': self.body, 'issuetype': { 'name': self.issuetype }, 'priority': self.pri_mapping[self.impact], } new_issue = jira.create_issue(fields=options) self.ticket = new_issue [self._add_watcher(new_issue, w) for w in self.watchers] return True else: return False def close(self): """Return bool representing success or failure for closing issue If issue doesn't exist, will return False because it can't close. Returns: success (bool) """ jira = self.jira finished_transition = self.finished_transition if self.exists(): # Fetch the transitions that we can put the current issue into. # Search through these for the provided ``finished_transition`` # from init. If not found, raise error. tkt = self.ticket transitions = jira.transitions(tkt) transition_ids = [ t['id'] for t in transitions if t['name'] == self.finished_transition ] if not transition_ids: raise ValueError( 'Transition "{}" not found'.format(finished_transition)) t = transition_ids[0] jira.transition_issue(tkt, t) else: return False def _add_watcher(self, issue, watcher): """Add watcher to issue""" self.jira.add_watcher(issue, watcher)
class JiraSender(object): tpl = Template('\n'.join( [ '*Current State*: $state', '*Host*: $host_name', '*Service*: $service_name', '*Output*: $output', ] )) def __init__(self): self.rest_api_user = settings.JIRA_USER_NAME self.rest_api_password = settings.JIRA_USER_PASSWORD self.rest_api_url = settings.JIRA_ENDPOINT self.jira = JIRA(basic_auth=(self.rest_api_user, self.rest_api_password), options={'server': self.rest_api_url}, timeout=30) def __get_param(self, data: dict, name: str, default_value=None): val = data.get(name) if val is None: val = default_value return val def create_ticket(self, project: str, data: dict): host_name = self.__get_param(data, 'host_name') service_name = self.__get_param(data, 'service_name') status = self.__get_param(data, 'status') service_state = status.name if status is not None else 'FAIL' output = self.__get_param(data, 'output', '') assignee = self.__get_param(data, 'assignee', []) watchers = self.__get_param(data, 'watchers', []) tags = self.__get_param(data, 'tags', []) priority = self.__get_param(data, 'priority', settings.JIRA_PRIORITY) issue_type = self.__get_param(data, 'issue_type', settings.JIRA_ISSUE_TYPE) transition_resolved_id = self.__get_param(data, 'transition_resolved_id', settings.JIRA_TRANSITION_RESOLVED_ID) close_when_restore = self.__get_param(data, 'close_when_restore', settings.JIRA_CLOSE_WHEN_RESTORE) post_comments = self.__get_param(data, 'post_comments', settings.JIRA_POST_COMMENTS) triggering_status = self.__get_param(data, 'triggering_status', settings.JIRA_TRIGGERING_STATUS) try: if isinstance(watchers, str): watchers_list = re.findall(r'[^,\s]+', watchers) elif isinstance(watchers, list): watchers_list = watchers else: watchers_list = [] if isinstance(tags, str): labels = re.findall(r'[^,\s]+', tags) elif isinstance(tags, list): labels = tags else: labels = [] labels.append('nogios') subject = 'NOGIOS: {} {}'.format(host_name, service_name) search_filter = 'summary ~ "{}" AND status not in (Resolved, Closed, Done) AND project="{}"'\ .format(subject, project) existing_issues = self.jira.search_issues(search_filter) issue_dict = { 'project': project, 'summary': subject, 'description': self.tpl.substitute({ 'host_name': host_name, 'service_name': service_name, 'state': service_state, 'output': output, }), 'issuetype': {'name': issue_type}, 'priority': {'name': priority} } if existing_issues is None or len(existing_issues) == 0: if service_state == triggering_status or service_state == settings.JIRA_TRIGGERING_STATUS: new_issue = self.jira.create_issue(fields=issue_dict) try: new_issue.update(fields={"labels": labels}) except JIRAError as e: print("Error: unable to add labels to a Jira ticket:", str(e)) for watcher in watchers_list: try: self.jira.add_watcher(new_issue.id, watcher) except JIRAError as e: print("Error: unable to add watchers to a Jira ticket:", str(e)) self.__add_comments(post_comments, new_issue.key, issue_dict['description']) if re.match(r'^[\S]+\@[\S]+$', assignee) is not None: self.jira.assign_issue(new_issue, assignee) else: current_issue = existing_issues[0] if service_state == settings.JIRA_TYPE_OK and current_issue.fields.assignee is None: self.__add_comments(post_comments, current_issue.key, issue_dict['description']) if close_when_restore: self.jira.transition_issue(current_issue.key, transition=transition_resolved_id, # resolution={'id': RESOLUTION_ID} ) else: self.__add_comments(post_comments, current_issue.key, issue_dict['description']) except Exception as ex: print("ERROR: unable to create Jira ticket:", ex) def __add_comments(self, post_comments, issue_key: str, comment_text: str): if post_comments: self.jira.add_comment(issue_key, comment_text)
class JiraWrapper: JIRA_REQUEST = 'project={} AND labels in ({})' def __init__(self, args, url, user, password, jira_project, assignee, check_functional_errors, check_performance_degradation, check_missed_thresholds, performance_degradation_rate, missed_thresholds_rate, issue_type='Bug', labels=None, watchers=None, jira_epic_key=None): self.valid = True self.args = args self.url = url self.password = password self.user = user self.check_functional_errors = check_functional_errors self.check_performance_degradation = check_performance_degradation self.check_missed_thresholds = check_missed_thresholds self.performance_degradation_rate = performance_degradation_rate self.missed_thresholds_rate = missed_thresholds_rate try: self.connect() except Exception: self.valid = False return self.projects = [project.key for project in self.client.projects()] self.project = jira_project if self.project not in self.projects: self.client.close() self.valid = False return self.assignee = assignee self.issue_type = issue_type self.labels = list() if labels: self.labels = [label.strip() for label in labels.split(",")] self.watchers = list() if watchers: self.watchers = [ watcher.strip() for watcher in watchers.split(",") ] self.jira_epic_key = jira_epic_key self.client.close() def connect(self): self.client = JIRA(self.url, basic_auth=(self.user, self.password)) def create_issue(self, title, priority, description, issue_hash, attachments=None, get_or_create=True, additional_labels=None): _labels = [issue_hash] if additional_labels and isinstance(additional_labels, list): _labels.extend(additional_labels) _labels.extend(self.labels) issue_data = { 'project': { 'key': self.project }, 'summary': title, 'description': description, 'issuetype': { 'name': self.issue_type }, 'assignee': { 'name': self.assignee }, 'priority': { 'name': priority }, 'labels': _labels } jira_request = self.JIRA_REQUEST.format(issue_data["project"]["key"], issue_hash) if get_or_create: issue, created = self.get_or_create_issue(jira_request, issue_data) else: issue = self.post_issue(issue_data) created = True if attachments: for attachment in attachments: if 'binary_content' in attachment: self.add_attachment( issue.key, attachment=attachment['binary_content'], filename=attachment['message']) for watcher in self.watchers: self.client.add_watcher(issue.id, watcher) if self.jira_epic_key: self.client.add_issues_to_epic(self.jira_epic_key, [issue.id]) return issue, created def add_attachment(self, issue_key, attachment, filename=None): issue = self.client.issue(issue_key) for _ in issue.fields.attachment: if _.filename == filename: return self.client.add_attachment(issue, attachment, filename) def post_issue(self, issue_data): issue = self.client.create_issue(fields=issue_data) print("Issue " + issue.key + " created." + " Description - " + issue_data['summary']) return issue def get_or_create_issue(self, search_string, issue_data): issuetype = issue_data['issuetype']['name'] created = False jira_results = self.client.search_issues(search_string) issues = [] for each in jira_results: if each.fields.summary == issue_data.get('summary', None): issues.append(each) if len(issues) == 1: issue = issues[0] if len(issues) > 1: print(' more then 1 issue with the same summary') else: print(issuetype + 'issue already exists:' + issue.key) else: issue = self.post_issue(issue_data) created = True return issue, created @staticmethod def create_functional_error_description(error, arguments): title = "Functional error in test: " + str(arguments['simulation']) + ". Request \"" \ + str(error['Request name']) + "\"." description = "{panel:title=" + title + \ "|borderStyle=solid|borderColor=#ccc|titleBGColor=#23b7c9|bgColor=#d7f0f3} \n" description += "h3. Request description\n" if error['Request name']: description += "*Request name*: " + error['Request name'] + "\n" if error['Method']: description += "*HTTP Method*: " + error['Method'] + "\n" if error['Request URL']: description += "*Request URL*: " + error['Request URL'] + "\n" if error['Request_params'] and str(error['Request_params']) != " ": description += "*Request params*: " + str( error['Request_params'])[2:-2].replace(" ", "\n") + "\n" if error['Request headers']: description += "*Request headers*: {code}" headers = str(error['Request headers']).replace(": ", ":") for header in headers.split(" "): description += header + "\n" description += "{code}\n" description += "---- \n h3. Error description\n" if error['Error count']: description += "*Error count*: " + str( error['Error count']) + ";\n" if error['Response code']: description += "*Response code*: " + error['Response code'] + ";\n" if error['Error_message']: description += "*Error message*: {color:red}" + str( error['Error_message']) + "{color}\n" if error['Response']: if len(str(error['Response'])) < 55000: description += "---- \n h3. Response body {code:html}" + str( error['Response']) + "{code}" else: description += "---- \n See response body attached.\n" description += "{panel}" return description @staticmethod def get_functional_error_hash_code(error, arguments): error_str = arguments['simulation'] + "_" + error['Request URL'] + "_" + str(error['Error_message']) + "_" \ + error['Request name'] return hashlib.sha256(error_str.strip().encode('utf-8')).hexdigest() @staticmethod def create_performance_degradation_description( performance_degradation_rate, compare_with_baseline, arguments): title = "Performance degradation in test: " + str( arguments['simulation']) description = "{panel:title=" + title + \ "|borderStyle=solid|borderColor=#ccc|titleBGColor=#23b7c9|bgColor=#d7f0f3} \n" description += "{color:red}" + "Test performance degradation is {}% compared to the baseline."\ .format(performance_degradation_rate) + "{color} \n" description += "h3. The following requests are slower than baseline:\n" for request in compare_with_baseline: description += "\"{}\" reached {} ms by {}. Baseline {} ms.\n".format( request['request_name'], request['response_time'], arguments['comparison_metric'], request['baseline']) description += "{panel}" return description @staticmethod def create_missed_thresholds_description(missed_threshold_rate, compare_with_thresholds, arguments): title = "Missed thresholds in test: " + str(arguments['simulation']) description = "{panel:title=" + title + \ "|borderStyle=solid|borderColor=#ccc|titleBGColor=#23b7c9|bgColor=#d7f0f3} \n" description += "{color:red}" + "Percentage of requests exceeding the threshold was {}%." \ .format(missed_threshold_rate) + "{color} \n" for color in ['yellow', 'red']: colored = False for th in compare_with_thresholds: if th['threshold'] == color: if not colored: description += f"h3. The following {color} thresholds were exceeded:\n" colored = True appendage = calculate_appendage(th['target']) description += f"\"{th['request_name']}\" {th['target']}{appendage} " \ f"with value {th['metric']}{appendage} " \ f"exceeded threshold of {th[color]}{appendage}\n" description += "{panel}" return description def report_errors(self, aggregated_errors): for error in aggregated_errors: issue_hash = self.get_functional_error_hash_code( aggregated_errors[error], self.args) title = "Functional error in test: " + str(self.args['simulation']) + ". Request \"" \ + str(aggregated_errors[error]['Request name']) + "\" failed with error message: " \ + str(aggregated_errors[error]['Error_message'])[0:100] description = self.create_functional_error_description( aggregated_errors[error], self.args) if len(str(aggregated_errors[error]['Response'])) < 55000: self.create_issue(title, 'Major', description, issue_hash) else: content = io.StringIO() content.write(str(aggregated_errors[error]['Response'])) attachment = { "binary_content": content, "message": "response_body.txt" } self.create_issue(title, 'Major', description, issue_hash, [attachment]) def report_performance_degradation(self, performance_degradation_rate, compare_with_baseline): issue_hash = hashlib.sha256("{} performance degradation".format( self.args['simulation']).strip().encode('utf-8')).hexdigest() title = "Performance degradation in test: " + str( self.args['simulation']) description = self.create_performance_degradation_description( performance_degradation_rate, compare_with_baseline, self.args) self.create_issue(title, 'Major', description, issue_hash) def report_missed_thresholds(self, missed_threshold_rate, compare_with_thresholds): issue_hash = hashlib.sha256("{} missed thresholds".format( self.args['simulation']).strip().encode('utf-8')).hexdigest() title = "Missed thresholds in test: " + str(self.args['simulation']) description = self.create_missed_thresholds_description( missed_threshold_rate, compare_with_thresholds, self.args) self.create_issue(title, 'Major', description, issue_hash)
def create_jira_ticket(summary, description, **kwargs): """ Create a new jira ticket, returning the associated number. Examples: Synchronously create a jira ticket:: create_jira_ticket("Test Ticket", "This is a test") Asynchronously create a jira ticket:: create_jira_ticket.delay("Test Ticket", "This is a test") Inputs: .. note:: watchers and watcher_group are mutually exclusive. :summary: The ticket summary :description: The ticket description :assignee: Who the ticket should be assigned to. Defaults to "-1" which is analogous to selecting "automatic" on the JIRA web form. :reporter: Who created the ticket (or is responsible for QCing it). Defaults to "automaticagent". :issuetype: The type of issue. Defaults to "Task". :project: The project the ticket should be created in. Defaults to "ST", which is Product Support. :priority: Ticket Priority. Defaults to "Major". :components: A list of components this ticket belongs to. :watchers: A list of user names to add as watchers of this ticket. :watcher_group: A group to assign as watchesr. Output: .. note:: The instance isn't returned because we need the ability to pass the results to another asynchronous task without blocking, which requires that all arguments be serializable. The ticket key which corresponds to the created JIRA ticket. """ jira = JIRA(options=options, basic_auth=housekeeping_auth) assignee = {'name': kwargs.setdefault('assignee', '-1')} reporter = {'name': kwargs.setdefault('reporter', 'automationagent')} issuetype = {'name': kwargs.setdefault('issuetype', 'Task')} project = {'key': kwargs.setdefault('project', 'ST')} priority = {'name': kwargs.setdefault('priority', 'Major')} components = [{ 'name': name } for name in kwargs.setdefault('components', [])] watchers = kwargs.setdefault('watchers', set()) if 'watcher_group' in kwargs: watchers = watchers.union( jira.group_members(kwargs['watcher_group']).keys()) if assignee == reporter: raise ValueError("Assignee and reporter must be different.") fields = { 'project': project, 'summary': summary, 'description': description, 'issuetype': issuetype, 'priority': priority, 'reporter': reporter, 'assignee': assignee, 'components': components, } issue = jira.create_issue(fields=fields) for watcher in watchers: jira.add_watcher(issue, watcher) return issue.key
class JiraWrapper(object): JIRA_REQUEST = 'project={} AND (description ~ "{}" OR labels in ({}))' def __init__(self, url, user, password, project, fields=None): self.valid = True self.url = url self.password = password self.user = user try: self.connect() except: self.valid = False return self.projects = [project.key for project in self.client.projects()] self.project = project.upper() if self.project not in self.projects: self.client.close() self.valid = False return self.fields = {} self.watchers = [] if isinstance(fields, dict): if 'watchers' in fields.keys(): self.watchers = [ item.strip() for item in fields.pop('watchers').split(",") ] all_jira_fields = self.client.fields() for key, value in fields.items(): if value: if isinstance( value, str ) and const.JIRA_FIELD_DO_NOT_USE_VALUE in value: continue jira_keys = [ item for item in all_jira_fields if item["id"] == key ] if not jira_keys: jira_keys = [ item for item in all_jira_fields if item["name"].lower() == key.lower().replace( '_', ' ') ] if len(jira_keys) == 1: jira_key = jira_keys[0] key_type = jira_key['schema']['type'] else: logging.warning( f'Cannot recognize field {key}. This field will not be used.' ) continue if key_type in ['string', 'number', 'any'] or isinstance( value, dict): _value = value elif key_type == 'array': if isinstance(value, str): _value = [ item.strip() for item in value.split(",") ] elif isinstance(value, int): _value = [value] else: _value = {'name': value} self.fields[jira_key['id']] = _value if not self.fields.get('issuetype', None): self.fields['issuetype'] = {'name': '!default_issuetype'} self.client.close() self.created_jira_tickets = list() def connect(self): self.client = JIRA(self.url, basic_auth=(self.user, self.password)) def markdown_to_jira_markdown(self, content): return content.replace("###", "h3.").replace("**", "*") def create_issue(self, title, priority, description, issue_hash, attachments=None, get_or_create=True, additional_labels=None): def replace_defaults(value): if isinstance(value, str) and const.JIRA_FIELD_USE_DEFAULT_VALUE in value: for default_key in default_fields.keys(): if default_key in value: value = value.replace(default_key, default_fields[default_key]) return value default_fields = { '!default_issuetype': 'Bug', '!default_summary': title, '!default_description': description, '!default_priority': priority } description = self.markdown_to_jira_markdown(description) issue_data = { 'project': { 'key': self.project }, 'issuetype': 'Bug', 'summary': title, 'description': description, 'priority': { 'name': priority } } fields = deepcopy(self.fields) for key, value in fields.items(): if isinstance(value, str): if const.JIRA_FIELD_DO_NOT_USE_VALUE in value: issue_data.pop(key) else: issue_data[key] = replace_defaults(value) elif isinstance(value, list): for item in value: value[value.index(item)] = replace_defaults(item) if issue_data.get(key): issue_data[key].extend(value) else: issue_data[key] = value elif isinstance(value, dict): for _key, _value in value.items(): value[_key] = replace_defaults(_value) issue_data[key] = value elif not key in issue_data: issue_data[key] = value else: logging.warning( 'field {} is already set and has \'{}\' value'.format( key, issue_data[key])) _labels = [] if additional_labels and isinstance(additional_labels, list): _labels.extend(additional_labels) if issue_data.get('labels', None): issue_data['labels'].extend(_labels) else: issue_data['labels'] = _labels jira_request = self.JIRA_REQUEST.format(issue_data["project"]["key"], issue_hash, issue_hash) if get_or_create: issue, created = self.get_or_create_issue(jira_request, issue_data) else: issue = self.post_issue(issue_data) created = True try: if attachments: for attachment in attachments: if 'binary_content' in attachment: self.add_attachment( issue.key, attachment=attachment['binary_content'], filename=attachment['message']) for watcher in self.watchers: self.client.add_watcher(issue.id, watcher) except: if os.environ.get("debug", False): logging.error(format_exc()) finally: self.created_jira_tickets.append({ 'description': issue.fields.summary, 'priority': issue.fields.priority, 'key': issue.key, 'link': self.url + '/browse/' + issue.key, 'new': created, 'assignee': issue.fields.assignee, 'status': issue.fields.status.name, 'open_date': issue.fields.created }) return issue, created def add_attachment(self, issue_key, attachment, filename=None): issue = self.client.issue(issue_key) for _ in issue.fields.attachment: if _.filename == filename: return self.client.add_attachment(issue, attachment, filename) def post_issue(self, issue_data): issue = self.client.create_issue(fields=issue_data) logging.info( f' \u2713 {issue_data["issuetype"]["name"]} was created: {issue.key}' ) return issue def get_or_create_issue(self, search_string, issue_data): issuetype = issue_data['issuetype'] created = False jira_results = self.client.search_issues(search_string) issues = [] for each in jira_results: if each.fields.summary == issue_data.get('summary', None): issues.append(each) if len(issues) == 1: issue = issues[0] if len(issues) > 1: logging.error(' more then 1 issue with the same summary') else: logging.info( f' {issuetype["name"]} already exists: {issue.key}') else: issue = self.post_issue(issue_data) created = True return issue, created def add_comment_to_issue(self, issue, data): return self.client.add_comment(issue, data) def get_created_tickets(self): return self.created_jira_tickets
class CoachesHelpDesk: def __init__(self, domain='jira.fiware.org'): self.base_url = 'https://{}'.format(domain) self.user = JIRA_USER self.password = JIRA_PASSWORD options = {'server': self.base_url, 'verify': False} self.jira = JIRA(options=options, basic_auth=(self.user, self.password)) self.n_assignment = 0 self.n_clones = 0 self.n_renamed = 0 def assign_request(self): query = 'project = HELC AND issuetype = extRequest AND component = EMPTY' requests = sorted(self.jira.search_issues(query, maxResults=False), key=lambda item: item.key) for request in requests: summary = request.fields.summary if re.search(r'\[SPAM\]', summary): request.update(fields={'components': [{'name': 'SPAM'}]}) continue match = re.search(r'\[[^\]]+?\]', summary) if match: accelerator = match.group(0)[1:-1] if accelerator in accelerators_dict: components = {'name': accelerators_dict[accelerator]} # request.update(fields={'components':[components]}, assignee={'name': '-1'}) request.update(fields={'components': [components]}) if not request.fields.assignee: self.jira.assign_issue(request, '-1') self.n_assignment += 1 logging.info('updated request {}, accelerator= {}'.format( request, accelerator)) def clone_to_main(self): self._clone_tech() self._clone_lab() def _clone_tech(self): query = 'project = HELC AND issuetype = extRequest AND component = _TECH_ AND not assignee = EMPTY' requests = sorted(self.jira.search_issues(query, maxResults=False), key=lambda item: item.key) for request in requests: fields = { 'project': { 'key': 'HELP' }, 'components': [{ 'name': 'FIWARE-TECH-HELP' }], 'summary': request.fields.summary, 'description': request.fields.description, 'issuetype': { 'name': request.fields.issuetype.name }, 'priority': { 'name': request.fields.priority.name }, 'labels': request.fields.labels, 'assignee': { 'name': None }, 'reporter': { 'name': request.fields.reporter.name } } new_issue = self.jira.create_issue(fields=fields) self.jira.create_issue_link('relates to', new_issue, request) self.jira.add_watcher(new_issue, request.fields.assignee.name) self.jira.remove_watcher(new_issue, self.user) components = [{ 'name': comp.name } for comp in request.fields.components if comp.name != '_TECH_'] request.update(fields={'components': components}) # self.jira.add_watcher(new_issue, request.fields.assignee.name) logging.info('CREATED TECH ISSUE: {} from {}'.format( new_issue, request)) self.n_clones += 1 def _clone_lab(self): query = 'project = HELC AND issuetype = extRequest AND component = _LAB_ AND not assignee = EMPTY' requests = sorted(self.jira.search_issues(query, maxResults=False), key=lambda item: item.key) for request in requests: fields = { 'project': { 'key': 'HELP' }, 'components': [{ 'name': 'FIWARE-LAB-HELP' }], 'summary': request.fields.summary, 'description': request.fields.description, 'issuetype': { 'name': request.fields.issuetype.name }, 'priority': { 'name': request.fields.priority.name }, 'labels': request.fields.labels, 'assignee': { 'name': None }, 'reporter': { 'name': request.fields.reporter.name } } new_issue = self.jira.create_issue(fields=fields) self.jira.create_issue_link('relates to', new_issue, request) self.jira.add_watcher(new_issue, request.fields.assignee.name) self.jira.remove_watcher(new_issue, self.user) components = [{ 'name': comp.name } for comp in request.fields.components if comp.name != '_LAB_'] request.update(fields={'components': components}) logging.info('CREATED LAB ISSUE: {} from {}'.format( new_issue, request)) self.n_clones += 1 def naming(self): query = 'project = HELC AND issuetype = extRequest AND status = Closed and updated >= -1d' requests = sorted(self.jira.search_issues(query, maxResults=False), key=lambda item: item.key) for request in requests: component = request.fields.components[0].name summary = request.fields.summary if re.match(r'FIWARE.Request.Coach.{}'.format(component), summary): continue summary = re.sub(r'\[[^\]]+?\]', '', summary) summary = 'FIWARE.Request.Coach.{}.{}'.format( component, summary.strip()) request.update(summary=summary) logging.info('{} {} {} {}'.format(request, request.fields.status, component, summary)) self.n_renamed += 1
def create_jira_ticket(summary, description, **kwargs): """ Create a new jira ticket, returning the associated number. Examples: Synchronously create a jira ticket:: create_jira_ticket("Test Ticket", "This is a test") Asynchronously create a jira ticket:: create_jira_ticket.delay("Test Ticket", "This is a test") Inputs: .. note:: watchers and watcher_group are mutually exclusive. :summary: The ticket summary :description: The ticket description :assignee: Who the ticket should be assigned to. Defaults to "-1" which is analogous to selecting "automatic" on the JIRA web form. :reporter: Who created the ticket (or is responsible for QCing it). Defaults to "automaticagent". :issuetype: The type of issue. Defaults to "Task". :project: The project the ticket should be created in. Defaults to "ST", which is Product Support. :priority: Ticket Priority. Defaults to "Major". :components: A list of components this ticket belongs to. :watchers: A list of user names to add as watchers of this ticket. :watcher_group: A group to assign as watchesr. Output: .. note:: The instance isn't returned because we need the ability to pass the results to another asynchronous task without blocking, which requires that all arguments be serializable. The ticket key which corresponds to the created JIRA ticket. """ jira = JIRA(options=options, basic_auth=housekeeping_auth) assignee = {'name': kwargs.setdefault('assignee', '-1')} reporter = {'name': kwargs.setdefault('reporter', 'automationagent')} issuetype = {'name': kwargs.setdefault('issuetype', 'Task')} project = {'key': kwargs.setdefault('project', 'ST')} priority = {'name': kwargs.setdefault('priority', 'Major')} components = [{'name': name} for name in kwargs.setdefault('components', [])] watchers = kwargs.setdefault('watchers', set()) if 'watcher_group' in kwargs: watchers = watchers.union( jira.group_members(kwargs['watcher_group']).keys()) if assignee == reporter: raise ValueError("Assignee and reporter must be different.") fields = { 'project': project, 'summary': summary, 'description': description, 'issuetype': issuetype, 'priority': priority, 'reporter': reporter, 'assignee': assignee, 'components': components, } issue = jira.create_issue(fields=fields) for watcher in watchers: jira.add_watcher(issue, watcher) return issue.key
class AugurJira(object): """ A thin wrapper around the Jira module providing some refinement for things like fields, convenience methods for ticket actions and awareness for Augur-specific data types. """ jira = None def __init__(self, server=None, username=None, password=None): self.logger = logging.getLogger("augurjira") self.server = server or settings.main.integrations.jira.instance self.username = username or settings.main.integrations.jira.username self.password = password or settings.main.integrations.jira.password self.fields = None self.jira = JIRA(basic_auth=(self.username, self.password), server=self.server, options={"agile_rest_path": "agile"}) self._field_map = {} self._default_fields = munchify({ "summary": None, "description": None, "status": None, "priority": None, "parent": None, "resolution": None, "epic link": None, "dev team": None, "labels": None, "issuelinks": None, "development": None, "reporter": None, "assignee": None, "issuetype": None, "project": None, "creator": None, "attachment": None, "worklog": None, "story points": None, "changelog": None }) self.fields = api.get_memory_cached_data('custom_fields') if not self.fields: fields = api.memory_cache_data(self.jira.fields(), 'custom_fields') self.fields = {f['name'].lower(): munchify(f) for f in fields} default_fields = {} for df, val in self._default_fields.items(): default_fields[df] = self.get_field_by_name(df) self._default_fields = munchify(default_fields) @property def default_fields(self): """ Returns a dict containing the friendly name of fields as keys and jira's proper field names as values. :return: dict """ return self._default_fields def get_field_by_name(self, name): """ Returns the true field name of a jira field based on its friendly name :param name: The friendly name of the field :return: A string with the true name of a field. """ assert self.fields try: _name = name.lower() if _name.lower() in self.fields: return self.fields[_name]['id'] else: return name except (KeyError, ValueError): return name def link_issues(self, link_type, inward, outward, comment=None): """ Establishes a link in jira between two issues :param link_type: A string indicating the relationship from the inward to the outward (Example: "is part of this release") :param inward: Can be one of: Issue object, Issue dict, Issue key string :param outward: Can be one of: Issue object, Issue dict, Issue key string :param comment: None or a string with the comment associated with the link :return: No return value. """ "" if isinstance(inward, dict): inward_key = inward['key'] elif isinstance(inward, Issue): inward_key = inward.key elif isinstance(inward, str): inward_key = inward else: raise TypeError("'inward' parameter is not of a valid type") if isinstance(outward, dict): outward_key = outward['key'] elif isinstance(outward, Issue): outward_key = outward.key elif isinstance(outward, str): outward_key = outward else: raise TypeError("'outward' parameter is not of a valid type") self.jira.create_issue_link(link_type, inward_key, outward_key, comment) def create_ticket(self, create_fields, update_fields=None, watchers=None): """ Create the ticket with the required fields above. The other keyword arguments can be used for other fields although the values must be in the correct format. :param update_fields: :param create_fields: All fields to include in the creation of the ticket. Keys include: project: A string with project key name (required) issuetype: A dictionary containing issuetype info (see Jira API docs) (required) summary: A string (required) description: A string :param update_fields: A dictionary containing reporter info (see Jira API docs) :param watchers: A list of usernames that will be added to the watch list. :return: Return an Issue object or None if failed. """ try: ticket = self.jira.create_issue(create_fields) if ticket: try: # now update the remaining values (if any) # we can't do this earlier because assignee and reporter can't be set during creation. if update_fields and len(update_fields) > 0: ticket.update(update_fields) except Exception as e: self.logger.warning( "Ticket was created but not updated due to exception: %s" % e.message) try: if watchers and isinstance(watchers, (list, tuple)): [self.jira.add_watcher(ticket, w) for w in watchers] except Exception as e: self.logger.warning( "Unable to add watcher(s) due to exception: %s" % e.message) return ticket except Exception as e: self.logger.error("Failed to create ticket: %s", e.message) return None
class JiraOperations(object): """ Base class for interaction with JIRA """ def __init__(self, config): # do not print excess warnings urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # JIRA configuration from config.json/DDB self.config = config # JIRA url self.server = self.config.jira.server # JIRA established session self.session = None if self.config.jira.enabled: self.login_oauth() else: logging.debug("JIRA integration is disabled") @property def current_user(self): """ :return: JIRA user name, used for connection establishing """ return self.session.current_user() def login_oauth(self): """ Establish JIRA connection using oauth :return: boolean, if connection was successful. """ if not self.config.jira.credentials: logging.error("Failed to login jira (empty credentials)") return False try: self.session = JIRA(options={ 'server': self.server, 'verify': False }, oauth=self.config.jira.credentials["oauth"]) except JIRAError: logging.exception( f"Failed to create oauth session to {self.server}") return False logging.debug( f'JIRA session to {self.server} created successfully (oauth)') return True def login_basic(self): """ Establish JIRA connection using basic authentication :return: boolean, if connection was successful. """ if not self.config.jira.credentials: logging.error("Failed to login jira (empty credentials)") return False username = self.config.jira.credentials["basic"]["username"] password = self.config.jira.credentials["basic"]["password"] options = {'server': self.server, 'verify': False} try: self.session = JIRA(options, basic_auth=(username, password)) except Exception: logging.exception( f"Failed to create basic session to {self.server}") return False logging.debug( f'JIRA session to {self.server} created successfully (basic)') return True def ticket_url(self, ticket_id): """ :return: URL to `ticket_id` """ return f"{self.server}/browse/{ticket_id}" def ticket_assignee(self, ticket_id): """ :param ticket_id: JIRA ticket :return: name of current assignee for ticket """ ticket = self.session.issue(ticket_id) return ticket.fields.assignee.name def find_valid_assignee(self, project, assignees): """ Check what record from given list of possible assignees can be used as assignee for given project. :param project: name of Jira project to perform check against :param assignees: list of possible assignees :return: """ for assignee in assignees: if assignee is None: continue try: users = self.session.search_assignable_users_for_projects( assignee, project) except Exception: continue # check only exact matches if len(users) == 1: return users[0].name return None def create_ticket(self, issue_data): """ Create a JIRA ticket :param issue_data: a dict containing field names and the values to use """ resp = self.session.create_issue(fields=issue_data) logging.debug(f"Created jira ticket {self.ticket_url(resp.key)}") return resp.key def create_issue_link(self, inward_issue, outward_issue): """ Linking JIRA tickets with 'relates to' link :return: boolean, if linking was successful """ if not (inward_issue or outward_issue): return False try: # JIRA comes with default types of links: # 1) relates to / relates to, # 2) duplicates / is duplicated by, # 3) blocks / is blocked by # 4) clones / is cloned by link_type = "relates to" self.session.create_issue_link(type=link_type, inwardIssue=inward_issue, outwardIssue=outward_issue) except Exception: logging.exception( f"Failed to create issue link {inward_issue} -> {outward_issue}" ) return False logging.debug(f"Created issue link {inward_issue} -> {outward_issue}") return True def assign_user(self, ticket_id, assinee_name): """ Assign `ticket_id` to `assinee_name`. :return: boolean, if assigning was successful """ if not (ticket_id or assinee_name): return False try: issue = self.session.issue(ticket_id) issue.update(assignee={'name': assinee_name}) except Exception: logging.exception( f"Failed to assign {ticket_id} to {assinee_name}") return False logging.debug(f"Assigned {ticket_id} to {assinee_name}") return True def add_label(self, ticket_id, label): """ add label to `ticket_id`. :return: boolean, if label update was successful """ if not (ticket_id and label): return False try: issue = self.session.issue(ticket_id) issue.fields.labels.append(label) issue.update(fields={"labels": issue.fields.labels}) except Exception: logging.exception(f"Failed to add {label} to {ticket_id}") return False logging.debug(f"Added label {label} to {ticket_id}") return True def update_ticket(self, ticket_id, updated_issue_data): """ Update JIRA ticket fields as in self.create_ticket(), but for existing ticket :param ticket_id: ticket Id to update :param updated_issue_data: a dict containing field names and the values to use :return: boolean, if updating was successful """ try: issue = self.session.issue(ticket_id) issue.update(updated_issue_data) except Exception: logging.exception(f"Failed to update {ticket_id}") return False logging.debug(f"Updated {ticket_id}") return True def add_comment(self, ticket_id, comment): """ Add comment to JIRA ticket :param ticket_id: ticket Id to add comment to :param comment: comment text :return: boolean, if operation was successful """ if ticket_id and comment: try: self.session.add_comment(ticket_id, comment) except Exception: logging.exception(f"Failed to add comment to {ticket_id}") return False return True def add_watcher(self, ticket_id, user): """ Adding jira ticket watcher. :param ticket_id: jira ticket id :param user: watcher user id :return: nothing """ self.session.add_watcher(ticket_id, user) def close_issue(self, ticket_id): """ Transition of ticket to `Closed` state. It checks if issue can be transitioned to `Closed` state. :param ticket_id: ticket Id to close :return: nothing """ if not ticket_id: return issue = self.session.issue(ticket_id) if issue.fields.status.name == "Closed": logging.debug(f"{ticket_id} is already closed") return for transition in self.session.transitions(issue): if transition['name'] == 'Close Issue': self.session.transition_issue(ticket_id, transition['id']) logging.debug(f"Closed {ticket_id}") break else: logging.error(f"{self.ticket_url(ticket_id)} can't be closed") return def resolve_issue(self, ticket_id): """ Transition of ticket to `Resolved` state. It checks if issue can be transitioned to `Resolved` state. :param ticket_id: ticket Id to resolve :return: nothing """ issue = self.session.issue(ticket_id) if issue.fields.status.name == "Resolved": logging.debug(f"{ticket_id} is already resolved") return for transition in self.session.transitions(issue): if transition['name'] == 'Resolve Issue': self.session.transition_issue(ticket_id, transition['id']) logging.debug(f"Resolved {ticket_id}") break else: logging.error(f"{self.ticket_url(ticket_id)} can't be resolved") return def reopen_issue(self, ticket_id): """ Transition of ticket to `Reopen Issue` state. It checks if issue can be transitioned to `Reopen Issue` state. :param ticket_id: ticket Id to reopen :return: nothing """ issue = self.session.issue(ticket_id) if issue.fields.status.name in ["Open", "Reopened"]: logging.debug(f"{ticket_id} is already opened") return for transition in self.session.transitions(issue): if transition['name'] == 'Reopen Issue': self.session.transition_issue(ticket_id, transition['id']) logging.debug(f"Reopened {ticket_id}") break else: logging.error(f"{self.ticket_url(ticket_id)} can't be reopened") return def add_attachment(self, ticket_id, filename, text): """ Add text as attachment with filename to JIRA ticket :param ticket_id: ticket Id to add attachment to :param filename: label for attachment :param text: attachment text :return: attachment object """ attachment = io.StringIO(text) filename = filename.replace(':', '-') return self.session.add_attachment(issue=ticket_id, attachment=attachment, filename=filename) @staticmethod def build_tags_table(tags): """ Build JIRA table from AWS tags dictionary :param tags: dict with tags :return: str with JIRA table """ if not tags: return "" desc = f"*Tags*:\n" desc += f"||Key||Value||\n" for key, value in tags.items(): desc += f"|{key}|{empty_converter(value)}|\n" return desc
class JiraWrapper(object): JIRA_REQUEST = 'project={} AND labels in ({})' def __init__(self, url, user, password, project, assignee, issue_type='Bug', labels=None, watchers=None, jira_epic_key=None): self.valid = True self.url = url self.password = password self.user = user try: self.connect() except: self.valid = False return self.projects = [project.key for project in self.client.projects()] self.project = project.upper() print(self.projects) if self.project not in self.projects: self.client.close() self.valid = False return self.assignee = assignee self.issue_type = issue_type self.labels = list() if labels: self.labels = [label.strip() for label in labels.split(",")] self.watchers = list() if watchers: self.watchers = [ watchers.strip() for watchers in watchers.split(",") ] self.jira_epic_key = jira_epic_key self.client.close() def connect(self): self.client = JIRA(self.url, basic_auth=(self.user, self.password)) def markdown_to_jira_markdown(self, content): return content.replace("###", "h3.").replace("**", "*") def create_issue(self, title, priority, description, issue_hash, attachments=None, get_or_create=True, additional_labels=None): description = self.markdown_to_jira_markdown(description) _labels = [issue_hash] if additional_labels and isinstance(additional_labels, list): _labels.extend(additional_labels) _labels.extend(self.labels) issue_data = { 'project': { 'key': self.project }, 'summary': re.sub('[^A-Za-z0-9//\. _]+', '', title), 'description': description, 'issuetype': { 'name': self.issue_type }, 'assignee': { 'name': self.assignee }, 'priority': { 'name': priority }, 'labels': _labels } jira_request = self.JIRA_REQUEST.format(issue_data["project"]["key"], issue_hash) if get_or_create: issue, created = self.get_or_create_issue(jira_request, issue_data) else: issue = self.post_issue(issue_data) created = True if attachments: for attachment in attachments: if 'binary_content' in attachment: self.add_attachment( issue.key, attachment=attachment['binary_content'], filename=attachment['message']) for watcher in self.watchers: self.client.add_watcher(issue.id, watcher) if self.jira_epic_key: self.client.add_issues_to_epic(self.jira_epic_key, [issue.id]) return issue, created def add_attachment(self, issue_key, attachment, filename=None): issue = self.client.issue(issue_key) for _ in issue.fields.attachment: if _.filename == filename: return self.client.add_attachment(issue, attachment, filename) def post_issue(self, issue_data): print(issue_data) issue = self.client.create_issue(fields=issue_data) logging.info( f' \u2713 {issue_data["issuetype"]["name"]} issue was created: {issue.key}' ) return issue def get_or_create_issue(self, search_string, issue_data): issuetype = issue_data['issuetype']['name'] created = False jira_results = self.client.search_issues(search_string) issues = [] for each in jira_results: if each.fields.summary == issue_data.get('summary', None): issues.append(each) if len(issues) == 1: issue = issues[0] if len(issues) > 1: print(' more then 1 issue with the same summary') else: print(f' {issuetype} issue already exists: {issue.key}') else: issue = self.post_issue(issue_data) created = True return issue, created
if not env == 'qa': # Transition RFD to Schedule state print "Scheduling the RFD" jira.transition_issue(rfd, 'Submit') jira.transition_issue(rfd, 'Start review') jira.transition_issue(rfd, 'Approve') jira.transition_issue(rfd, 'Schedule') if dba_needed: # Transition DBA subtask print "Scheduling the DBA subtask" #transitions = jira.transitions(rfd_dba_task) # print [(t['id'], t['name']) for t in transitions] jira.transition_issue(rfd_dba_task, 'Start review') jira.transition_issue(rfd_dba_task, 'Approve') jira.transition_issue(rfd_dba_task, 'Schedule') # Transition Jenkins subtask print "Scheduling the Jenkins subtask" #transitions = jira.transitions(rfd_jenkins_task) # print [(t['id'], t['name']) for t in transitions] jira.transition_issue(rfd_jenkins_task, 'Start review') jira.transition_issue(rfd_jenkins_task, 'Approve') jira.transition_issue(rfd_jenkins_task, 'Schedule') jira.add_watcher(rfd, project.lead.name) rfds.close() except JIRAError as e: print "Something bad happened while processing the %s application JIRA tickets." % app.upper( ) rfds.close()
class Util(object): """ """ def __init__(self, **kwargs): """ """ if 'config' in kwargs: self._config = kwargs['config'] else: logging.critical("config was not defined") raise Exception("config was not defined") if 'username' in kwargs: self._username = kwargs['username'] else: logging.critical("username was not defined") raise Exception("username was not defined") if 'password' in kwargs: self._password = kwargs['password'] else: logging.critical("password was not defined") raise Exception("password was not defined") if 'project' in kwargs: self._project = kwargs['project'] else: if 'project' in self._config: self._project = self._config['project'] logging.info( "project was set to '%s' from the configuration file" % self._project) else: self._project = DEFAULT_PROJECT logging.info("project was set to default '%s'" % self._project) if 'base_url' in kwargs: self._base_url = kwargs['base_url'] else: if 'base_url' in self._config: self._base_url = self._config['base_url'] logging.info( "base_url was set to '%s' from the configuration file" % self._base_url) else: self._base_url = DEFAULT_BASE_URL logging.info("base_url was set to default '%s'" % self._base_url) if 'add_missing_watchers' in kwargs: self._add_missing_watchers = kwargs['add_missing_watchers'] else: if 'add_missing_watchers' in self._config: self._add_missing_watchers = self._config[ 'add_missing_watchers'] logging.info( "add_missing_watchers was set to '%s' from the configuration file" % self._add_missing_watchers) else: self._add_missing_watchers = DEFAULT_ADD_MISSING_WATCHERS logging.info("add_missing_watchers was set to default '%s'" % self._add_missing_watchers) self._jira = None self._jra = None self._initialize() def setProject(self, project): """ :param project: :return: """ self._project = project def setAddMissingWatchers(self, add_missing_watchers): """ :param add_missing_watchers: :return: """ self._add_missing_watchers = add_missing_watchers def _initialize(self): """ :return: """ print("Attempting to connect to JIRA at '%s'" % self._base_url) self._jira = JIRA(self._base_url, basic_auth=(self._username, self._password)) print("Attempting to retrieve info for project '%s'" % self._project) self._jra = self._jira.project(self._project) def getReport(self): """ :return: """ self.report_misc() self.report_components() self.report_roles() self.report_versions() self.report_open_issues() def report_misc(self): """ :return: """ print(Fore.BLUE + "Project name '%s'" % self._jra.name) print(Fore.BLUE + "Project lead '%s'" % self._jra.lead.displayName) print(Style.RESET_ALL) def report_components(self): """ :return: """ components = self._jira.project_components(self._jra) if len(components) > 0: print(Fore.BLUE + "Here are the components") print(Style.RESET_ALL) for c in components: print(c.name) else: print(Fore.RED + "There are no components") print(Style.RESET_ALL) def report_roles(self): """ :return: """ roles = self._jira.project_roles(self._jra) if len(roles) > 0: print(Fore.BLUE + "Here are the roles") print(Style.RESET_ALL) for r in roles: print(r) else: print(Fore.RED + "There are no roles") print(Style.RESET_ALL) def report_versions(self): """ :return: """ versions = self._jira.project_versions(self._jra) if len(versions) > 0: print(Fore.BLUE + "Here are the versions") print(Style.RESET_ALL) for v in reversed(versions): print(v.name) else: print(Fore.RED + "There are no versions") print(Style.RESET_ALL) def report_watchers(self, issue): """ :param issue: :return: """ watcher = self._jira.watchers(issue) print("Issue '%s' has '%d' watcher(s)" % (issue.key, watcher.watchCount)) current_watchers_email = {} for watcher in watcher.watchers: current_watchers_email[watcher.emailAddress] = True print("'%s' - '%s'" % (watcher, watcher.emailAddress)) # watcher is instance of jira.resources.User: # print(watcher.emailAddress) for watcher_email in self._config['members_email_lookup']: if not watcher_email in current_watchers_email: print(Fore.RED + "member '%s' needs to be added as a watcher to '%s'" % (watcher_email, issue.key)) username = self._config['members_email_lookup'][watcher_email] print("Need to add username '%s'" % username) print(Style.RESET_ALL) if self._add_missing_watchers: self._jira.add_watcher(issue, username) print("Exiting") sys.exit(0) print(Style.RESET_ALL) def checkWatchers(self): """ :return: """ issues = self._jira.search_issues('project= LO AND status != Done', maxResults=DEFAULT_MAX_RESULTS) if len(issues) > 0: for issue in issues: self.report_watchers(issue) def report_open_issues(self): issues = self._jira.search_issues('project= LO AND status != Done', maxResults=DEFAULT_MAX_RESULTS) if len(issues) > 0: print(Fore.BLUE + "Found the following '%d' open issues" % len(issues)) print(Style.RESET_ALL) for issue in issues: summary = issue.fields.summary id = issue.id key = issue.key print("id '%s' key '%s' summary : '%s'" % (id, key, summary)) if DEFAULT_REPORT_WATCHERS: self._report_watchers(issue) print(Style.RESET_ALL) def getComments(self, key): """ :param key: :return: """ logging.info("Attempting to retrieve the issue with key '%s'" % key) issues = self._jira.search_issues('key = ' + key) if len(issues) > 1: raise Exception("Expected only one issue for '%s' but found '%d'" % (key, len(issues))) if len(issues) == 1: # comments = issues[0].fields.comment.comments # comments = issues[0].raw['fields']['comment']['comments'] comments = self._jira.comments(issues[0]) if len(comments) > 0: print("Found the following '%d' comments" % len(comments)) comment_ctr = 0 for comment_id in comments: print("-----------------------------------") comment_ctr += 1 comment = self._jira.comment(key, comment_id) author = comment.author.displayName date_created = comment.created body = comment.body print(Fore.BLUE + "%d. author '%s' date '%s'" % (comment_ctr, author, date_created)) print(Style.RESET_ALL) print(body)