Пример #1
0
    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)
Пример #2
0
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)
Пример #3
0
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)
Пример #4
0
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
Пример #5
0
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)
Пример #9
0
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)
Пример #10
0
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)
Пример #11
0
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
Пример #12
0
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
Пример #13
0
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
Пример #14
0
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
Пример #15
0
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
Пример #16
0
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
Пример #17
0
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()
Пример #19
0
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)