class JiraWrapper:
    '''Set of convenience methods around jira'''

    def __init__(self, url, login, password, sslcheck=False):
        self.url = url
        self.login = login
        self.options = {
                        'server': self.url,
                        'verify': sslcheck,
                       }
        authz = (login, password)
        self.jira = JIRA(options=self.options, basic_auth = authz)
        self.log = logging.getLogger('gerritwarden')

    def comment(self, issue, text):
        '''Add comment'''
        self.jira.add_comment(issue, text)

    def _get_tr_id_by_name(self, trans_available, name):
        '''Get transition id by target state name'''
        for trans in trans_available:
            if trans['to']['name'].lower() == name.lower():
                return trans['id']
        return None

    def transition(self, issue, state, comment, fields=None):
        '''Transition field with comment to specific state'''
        trans_available = self.jira.transitions(issue)
        trans_id = self._get_tr_id_by_name(trans_available, state)
        _issue = self.jira.issue(issue)
        if trans_id:
            self.jira.transition_issue(_issue, trans_id, comment=comment, fields=fields)
            return True
        else:
            return False

    def get_custom_fields(self, issue):
        c_fields = {}
        issue = self.jira.issue(issue)
        fields = issue.fields.__dict__
        for key, value in fields.items():
            if key.startswith('customfield'):
                c_fields[key] = value
        return c_fields

    def add_review_link(self, issue, reviewfield, reviewlink):
        '''Add a review link to issue. The link is added as an external link
        and as a value to Code Review field'''
        # Custom fields are starting with customfield_ prefix and a number
        # so we'll make sure that id will be enough
        try:
            int(reviewfield)
            reviewfield = "customfield_%s" % reviewfield
        except ValueError:
            str(reviewfield)
        c_fields = self.get_custom_fields(issue)
        s_issue = self.jira.issue(issue)
        try:
            rfield = c_fields[reviewfield]
        except KeyError:
            # There's no review field for this type of issue
            pass
        else:
            # TODO : make sure links are added once
            link_o = urlparse(reviewlink)
            changeid = link_o.path.split("/")[-1]
            app = link_o.hostname.split(".")[0]
            title = "%s - %s" % (app, changeid)
            link_object = {'url': reviewlink, 'title': title}
            gerrit_app = {'type': "gerrit", 'name': app}
            self.jira.add_remote_link(issue, object=link_object,
                                      globalId=reviewlink,
                                      application=gerrit_app)
            if c_fields.get(reviewfield):
                reviews = set(rfield.split("\n"))
                reviews.add(reviewlink)
                reviews_line = "\n".join(sorted(reviews))
                exec("s_issue.update(%s=reviews_line)" % reviewfield)
            else:
                exec("s_issue.update(%s=reviewlink)" % reviewfield)

    def is_connected(self):
        '''Check if connection to jira is still up'''
        if self.jira.server_info():
            return True
        else:
            return False
Esempio n. 2
0
def jiraConnect(user, pswd, host, issue):
    jira = JIRA(basic_auth=(user, pswd), options={'server': host})
    projects = jira.projects()
    for project in projects:
        if project.key == issue:
            issue_id = project.id
    if issue_id:
        tckt = prepSummary(issue)  
        #Creating ticket  
        new_issue = jira.create_issue(fields=tckt)
        print new_issue
        #Attaches the *.txt files under /tmp directory.
        files =  glob.glob(filepath)
        if len(files)==0:
            sendmail("failed","Issue created, however file attaching failed")
            exit(1)
        else:
        for file in files:
            jira.add_attachment(new_issue,file)
            jira.add_comment(new_issue, 'Required files are Attached!')
        sendmail("success","Ticket has been created with the required evidence attached!")
        
#main def
def main():
    #server, user credentials 
    user_name  = 'user'
    user_pass  = '******'
    server     = 'http://jiraexample.com:8080'
    issue_type = 'BUG'
    try:
        jiraConnect(user_name, user_pass, server, issue_type)
    except Exception, e:
        sendmail("failed", str(e))
        exit(1)
Esempio n. 3
0
class SRJira:
    
    def __init__(self, server, userName, password):
        server = 'https://snapdeal.atlassian.net'
        userName = '******'
        password = '******'
        options = {
            'server': server
        }
        self.jira = JIRA(options)

        self.jira = JIRA(basic_auth=(userName, password))
        
    def verifyIssueOwner(self, issueId, emailId):
        jiraIssue = self.jira.issue(issueId)
        if jiraIssue.fields.assignee.emailAddress == emailId:
            print 'Issue ', issueId , ' belongs to correct owner ', emailId
        else:
            print 'Issue ', issueId , ' correct owner is ', jiraIssue.fields.assignee.emailAddress, 'please check!'
            sys.exit(1)

    def verifyIssueState(self, issueId, issueState):
        jiraIssue = self.jira.issue(issueId)
        if jiraIssue.fields.status.name == issueState:
            print 'Issue ', issueId , ' is in correct state ', issueState
        else:
            print 'Issue ', issueId , ' state is ', jiraIssue.fields.status.name, 'please check!'
            sys.exit(1)
            
    def updateIssue(self, issueId, commitMessage):
        jiraIssue = self.jira.issue(issueId)
        comment = 'A commit with %s is done using this JIRA' % commitId
        print comment
        self.jira.add_comment(jiraIssue, comment)
Esempio n. 4
0
    def sent_to_jira(self, ticketId, result):
        """
        send the result to JIRA

        """
        options = {
            'server': jira_server
        }
        jira = JIRA(options, basic_auth=(jira_username, jira_password))

        # Get all projects viewable by anonymous users.
        projects = jira.projects()

        # Sort available project keys, then return the second, third, and fourth keys.
        keys = sorted([project.key for project in projects])[2:5]

        # Get an issue.
        issue = jira.issue(ticketId)

        # Add a comment to the issue.
        jira.add_comment(issue, result)

        # Move the ticket to Provision Completed.
        #print issue.fields.status
        try:    
            jira.transition_issue(issue, transitionId=provision_completed_transition_id, comment='Provision finished')
        except:
            print "Transition Error"
            self.logger.error("Transition Error")
def get_jira_ticket():
    """grabs ticket from jira ticket
    """

    """
    options = {
    'server': 'http://dev-jira-01.wernerds.net:8080',
    'autofix':True
    }
    """
    options = {
    'server': 'http://jira.wernerds.net',
    # 'verify': '/home/appadmin/scripts/build_cre/jira.cer',
    'autofix':True
    }

    logging_message('Creating Jira session...')
    pw = decrypt_key('autoappadmin')
    jira = JIRA(options, basic_auth=('autoappadmin', pw))
    projects = jira.projects()
    try:
        issue = jira.issue(sys.argv[1])
    except:
        print ('\n---------  Ticket has to be in Jira production example "CD-000" ----------')
        print ('\t\tExample: $> build_cre CD-000')
        print ('\t\tExample: $> /home/appadmin/scripts/build_cre/helper_deploy.sh CD-000\n')
        sys.exit()
    logging_message('Jira session created on: Base URL = %s, Version = %s, Ticket = %s' % (jira.server_info()['baseUrl'], jira.server_info()['version'], issue.key))
    
    """ This looks for any cards already in the Test Environment or Staging release approved that aren't the current card
    """
    current_cre = issue.fields.customfield_13128
    jira_search_string= 'project=CD and status ="Deployed to CRE" and "Application/Product"=SMART and "Test Environment"='+str(current_cre)
    issues_in_cre = jira.search_issues(jira_search_string)
    issues_boolean = len(issues_in_cre)>1 or (str(issues_in_cre)!="[]" and not (str(issue) in str(issues_in_cre)))
    print("jira search string ", jira_search_string)
    print("issues_in_cre ", issues_in_cre)
    print("This ticket is itself: ", issue, issues_in_cre)
    print("This ticket is itself: ", (str(issue) in str(issues_in_cre)))
    print("Number of issues in CRE: ", len(issues_in_cre))
    print("Boolean for checking issues in CRE: ", issues_boolean)
    jira_search_string= 'project=CD and status ="Testing Complete" and "Application/Product"=SMART and "Test Environment"='+str(current_cre)
    issues_in_cre2 = jira.search_issues(jira_search_string)
    issues_boolean = (issues_boolean)or(len(issues_in_cre2)>1 or (str(issues_in_cre2)!="[]" and not (str(issue) in str(issues_in_cre2))))
    print("issues bool", issues_boolean)
    if(issues_boolean):
        comment_str = "There is currently code in that CRE : "+str(issues_in_cre)+str(issues_in_cre2)+".  Please deploy after that code leaves."
        jira.add_comment(issue, comment_str)
        print("There is code in that CRE already. Exiting...")
        sys.exit(0)  
    return issue, jira
Esempio n. 6
0
    def handle_noargs(self, **options):
        jira = JIRA(basic_auth=(settings.JIRA_USERNAME, settings.JIRA_PASSWORD),
                    options={'server': settings.JIRA_BASE_URL})

        print "Logged in..."

        categories = [p.key for p in jira.projects()]
        tag_logged, _ = Tag.objects.get_or_create(name='_logged_')

        facts = Fact.objects \
                    .exclude(tags=tag_logged) \
                    .exclude(end_time=None) \
                    .filter(activity__category__name__in=categories)

        for f in facts:
            reopened = False
            try:
                issue_key = '%s-%s' % (f.category, f.activity.name)
                issue = jira.issue(issue_key)

                if  issue.fields.status.name in ('Closed', 'Resolve'):
                    reopened = True
                    # reopen to allow worklogs
                    jira.transition_issue(issue, TO_REOPEN)
                    jira.add_comment(issue, 'hamster2jira: Automatically reopened for a'
                                            'while to allow add a worklog')
            except JIRAError:
                continue

            spent = days_hours_minutes(f.duration)
            #and post the fact into dotproject!
            worklog = jira.add_worklog(issue, spent)
            worklog.update(comment=f.description)

            #create a tag to associate this worklog whit the fact
            wl_tag = Tag.objects.create(name='wl%s' % worklog.id)
            FactTag.objects.create(tag=wl_tag, fact=f)
            print "Succesfully log %s into %s" % (spent, issue_key)

            #then mark the fact as logged.
            FactTag.objects.create(fact=f, tag=tag_logged)
            if reopened:
                jira.transition_issue(issue, TO_CLOSE)
Esempio n. 7
0
def authenticate_and_addComment_in_jira(issue_id, comment):
	"""
	Authenticate jira  and adding comment in jira
	issue
	"""
	jira_url = 'https://issues.quickoffice.com'
	user_name = 'developer.synerzip'
	password = '******'
	
	# Set up JIRA server URL
	jira_options = {
	'server': jira_url
	}

	#Connect to JIRA server using above URL and authentication details
	jira=JIRA(options=jira_options,basic_auth=(user_name,password))
	projects = jira.projects()
	issue = jira.issue(issue_id)
	print issue
	print comment
	jira.add_comment(issue, comment)
Esempio n. 8
0
# Get all projects viewable by anonymous users.
projects = jira.projects()

print(projects)


new_issue = jira.create_issue(project={'key': 'TST'}, summary='New issue from jira-python',
                              description='Look into this one', issuetype={'name': 'Story'})

# Sort available project keys, then return the second, third, and fourth keys.
keys = sorted([project.key for project in projects])[2:5]

print(jira.search_issues('project=ACT and assignee=currentUser()'))

# Get an issue.
issue = jira.issue('TST-1')
print ('printing')
print issue

## Find all comments made by Atlassians on this issue.
#import re
#atl_comments = [comment for comment in issue.fields.comment.comments
                #if re.search(r'@atlassian.com$', comment.author.emailAddress)]

## Add a comment to the issue.
jira.add_comment(issue, 'I have added a comment to this issue via API')

## Change the issue's summary and description.
issue.update(summary="This issue has been updated", description='Changed the summary to be different.')
Esempio n. 9
0
class JiraRobot:
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    jira = None

    def connect_to_jira(self,
                        JIRAUsername=None,
                        JIRAPassword=None,
                        options=None):
        """
        Connect to a JIRA server.

            Arguments:
                |  JIRAUsername (string)  	|  (Optional) A JIRA Username you wish to authenticate with, will authorise with anonymous account if left empty      				|
                |  JIRAPassword (string)  	|  (Optional) If a username was specified and a password is not the user will be prompted for password at runtime 					|
                |  options (dictionary) 	|  (Optional) A dictionary of options that the JIRA connection will be initialied with 	                                            |

            This must be called first to be able to do most JIRA actions such as creating a new issue and assigning users.
            When connecting to JIRA you may need to authenticate a user. This can be done by passing in Username and Password as parameters. However, should you wish to not have a sensitive password saved in a file,  an option is available to not pass in a Password and a prompt will appear asking for the JIRA password at runtime.


            'connect to jira' can be called on its own and will default options to:
                '{'rest_api_version': '2', 'verify': True,
                'server': 'http://*****:*****@ssword 						| {'server': http://devjira01'} 	|
        """

        if JIRAUsername is not None and (JIRAPassword is ""
                                         or JIRAPassword is None):
            JIRAPassword = getpass.getpass("\nJIRA Password: "******"\nAuthentication to JIRA unsuccessful. Ensure the user used has sufficient access and that Username and Password were correct\n\n"
                )
                sys.exit(1)

        else:
            try:
                self.jira = JIRA(options=JIRAOptions,
                                 basic_auth=(str(JIRAUsername),
                                             str(JIRAPassword)))
            except:
                sys.__stdout__.write(
                    "\nAuthentication to JIRA unsuccessful. Ensure the user used has sufficient access and that Username and Password were correct\n\n"
                )
                sys.exit(1)

    def create_issue(self, issue_field_dict, assign_current_user=False):
        """
        Creates a new JIRA issue.

            Arguments:
                |  issue_field_dict (string)  			| A dictionary in the form of a string that the user can specify the issues fields and field values 			|
                |  assign_current_user (string/bool)  	| (Optional) A flag to assign the current user to the issue once it is successfully created, defaults to False	|

            Will create a new issue and returns an Issue Key.
            The user will use the issue_field_dict variable to specify the issues field and their respective values. This must be in the form of a string written as a dictionary
                e.g. {'FieldName':'FieldValue'}

            The field value can also be another dictionary (in some field cases this is needed)
                e.g. {'FieldName1':{'key':'value'}, 'FieldName2':FieldValue, 'FieldName3':{'name':'value'}}

            This Create Issue Example page (https://developer.atlassian.com/display/JIRADEV/JIRA+REST+API+Example+-+Create+Issue) is full of useful information in what is required for creating issues, including customfields and issue field value types, it is very important to get the value type right or an error will be thrown.


            Examples:
                |  *Keyword*        	|  *Parameters*   	| 														| 		|
                |  ${issue_field_dict} 	|  {'project':{'key': 'PROJ'}, 'summary':'Create New Issue', 'description':'Creating a new issue', 'issuetype':{'name': 'Bug'}} |    |
                |  connect to jira      |  asimmons         | options= {'http://devjira01'}                         |       |
                |  ${issue}				|  create issue 	|  ${issue_field_dict}									|      	|

                |  connect to jira      |  asimmons         | options= {'http://devjira01'}                         |  		|
                |  ${issue}				|  create issue 	|  ${issue_field_dict}									|  True |
        """
        issue_field_dict = eval(str(issue_field_dict))
        print issue_field_dict

        new_issue = self.jira.create_issue(issue_field_dict)
        if assign_current_user is True:
            self.assign_user_to_issue(new_issue, self.jira.current_user())
        return new_issue

    def create_issue_link(self,
                          link_type,
                          inwardissue,
                          outwardissue,
                          comment=None):
        """
        Create a link between two issues.

            Arguments:
                |  link_type (string)  	| The type of link									|
                |  inwardissue (string)  	| The issue to link from  							|
                |  outwardissue (string)  	| The issue to link to 								|
                |  comment (string)  		| (Optional) A comment to add when joining issues	|

            Example:
                |  *Keyword*        	|  *Parameters* | 									| 			|
                |  connect to jira      |  asimmons     | options= {'http://devjira01'}     |  			|
                |  ${issue}				|  create issue |  ${issue_field_dict}				|  True 	|
                |  create issue link	|  relates to   |  ${issue} 						|  PROJ-385	|
        """
        self.jira.create_issue_link(type=link_type,
                                    inwardIssue=str(inwardissue),
                                    outwardIssue=str(outwardissue))

    def assign_user_to_issue(self, issue, JIRAUsername):
        # TODO: Review docs
        """
        Adds a user to a specified issue's watcher list

        Arguments:
            |  issue (string)  		| A JIRA Issue that a user needs to be assigned to, can be an issue ID or Key		|
            |  JIRAUsername (string)  	| A JIRA Username to assign a user to an issue   									|

        Example:
           |  *Keyword*        		|  *Parameters* | 									|
           |  connect to jira       |  asimmons     | options= {'http://devjira01'}     |
           |  ${issue}				|  create issue |  ${issue_field_dict}				|
           |  assign user to issue	|  ${issue}		|  aSample 							|
        """
        self.jira.assign_issue(issue=issue, assignee=JIRAUsername)

    def get_current_user(self):
        """
        Returns the current user used in the Connect to JIRA keyword.
        """
        return self.jira.current_user()

    def add_watcher_to_issue(self, issue, JIRAUsername):
        """
        Adds a user to a specified issue's watcher list.

        Arguments:
            |  issue (string)  		| A JIRA Issue that a watcher needs added to, can be an issue ID or Key		|
            |  JIRAUsername (string)  	| A JIRA Username to add as a watcher to an issue   					|

        Example:
            |  *Keyword*        	|  *Parameters* | 								|		|
            |  connect to jira  |  asimmons     | options= {'http://devjira01'}     | 		|
            |  ${issue}				|  create issue |  ${issue_field_dict}			|  True |
            |  add watcher to issue	|  ${issue}		|  aSample 						| 		|

        """
        self.jira.add_watcher(issue=issue, watcher=JIRAUsername)

    def add_comment_to_issue(self, issue, comment, visibility=None):
        """
        Adds a comment to a specified issue from the current user.

            Arguments:
                |  issue (string)  		| A JIRA Issue that a watcher needs added to, can be an issue ID or Key	|
                |  comment (string)  		| A body of text to add as a comment to an issue   						|
                |  visibility (string)  	| (Optional)															|

            Example:
                |  *Keyword*        	|  *Parameters* | 									|		|
                |  connect to jira      |  asimmons     |  options= {'http://devjira01'}    | 		|
                |  ${issue}				|  create issue |  ${issue_field_dict}				|  True |
                |  add comment to issue	|  ${issue}		|  Starting work on this issue		| 		|
        """
        self.jira.add_comment(issue=issue, body=comment)

    def add_attachment_to_issue(self, issue, attachment, filename=None):
        """
        Uploads and attaches a file a specified issue. (Note: include the file extention when using the 'filename' option or this will change the file type.)

        Arguments:
            |   issue (string)  	| A JIRA Issue that a watcher needs added to, can be an issue ID or Key		|
            |   attachment (string) | A string pointing to a file location to upload and attach to the issue	|
            |   filename (string)  	| (Optional) A string to rename the file to upon attaching to the issue  	|

        Example:
            |  *Keyword*        		|  *Parameters* | 							         |						|
            |  connect to jira          |  asimmons     | options= {'http://devjira01'}      | 						|
            |  ${issue}					|  create issue |  ${issue_field_dict}	             |  True 			 	|
            |  add attchment to issue	|  ${issue}		|  ./logfile.text			         |  LogInformation.txt	|
        """
        self.jira.add_attachment(issue=issue,
                                 attachment=attachment,
                                 filename=filename)
Esempio n. 10
0
class JIRAReporter(IActionBase, TargetableAction):
    implements(IAction)

    id = 'JIRAReporter'
    name = 'JIRA Issue Reporter'
    actionContentInfo = IJIRAActionContentInfo 

    shouldExecuteInBatch = False

    def __init__(self):
        log.debug('[research] %s : initialized' % (self.id))
        super(JIRAReporter, self).__init__()

        self.connected = False
        self.jira = None

    def setupAction(self, dmd):
        log.debug('[research] setup : %s' % (self.name))
        self.guidManager = GUIDManager(dmd)
        self.dmd = dmd

    def executeOnTarget(self, notification, signal, target):
        self.setupAction(notification.dmd)

        log.debug('[research] execute : %s on %s' % (self.name, target))

        jiraURL = notification.content['jira_instance']
        jiraUser = notification.content['jira_user']
        jiraPass = notification.content['jira_password']

        issueProject = notification.content['issue_project']
        issueType = notification.content['issue_type']
        issuePriority = notification.content['issue_priority_key']
        customfields = notification.content['customfield_keypairs']
        eventRawData = notification.content['event_rawdata']
        serviceRoot = notification.content['service_group_root']

        summary = ''
        description = ''

        if (signal.clear):
            log.info('[research] event cleared : %s' % (target))
            description = notification.content['clear_issue_description']
        else:
            log.warn('[research] event detected : %s' % (target))
            summary = notification.content['issue_summary']
            description = notification.content['issue_description']

        actor = signal.event.occurrence[0].actor
        device = None
        if (actor.element_uuid):
            device = self.guidManager.getObject(actor.element_uuid)

        component = None
        if (actor.element_sub_uuid):
            component = self.guidManager.getObject(actor.element_sub_uuid)

        environ = {
            'dev': device, 'component': component, 'dmd': notification.dmd
        }

        data = _signalToContextDict(
            signal, self.options.get('zopeurl'),
            notification, self.guidManager
        )

        environ.update(data)

        if (environ.get('evt', None)):
            environ['evt'] = self._escapeEvent(environ['evt'])

        if (environ.get('clearEvt', None)):
            environ['clearEvt'] = self._escapeEvent(environ['clearEvt'])

        environ['user'] = getattr(self.dmd.ZenUsers, target, None)

        issueValues = {
            'summary' : summary,
            'description' : description,
            'eventraw' : eventRawData,
            'customfields' : customfields
        }

        log.debug('[research] base issue values : %s' % (issueValues))

        targetValues = {
            'project' : issueProject,
            'issuetype' : issueType,
            'priority' : issuePriority,
            'serviceroot' : serviceRoot 
        }

        log.debug('[research] base target values : %s' % (targetValues))

        self.connectJIRA(jiraURL, jiraUser, jiraPass)

        if (signal.clear):
            self.clearEventIssue(environ, targetValues, issueValues)
        else:
            self.createEventIssue(environ, targetValues, issueValues)

        log.debug("[research] event update reported : %s" % (jiraURL));

    def getActionableTargets(self, target):
        ids = [target.id]
        if isinstance(target, GroupSettings):
            ids = [x.id for x in target.getMemberUserSettings()]
        return ids

    def _escapeEvent(self, evt):
        """
        Escapes the relavent fields of an event context for event commands.
        """
        if evt.message:
            evt.message = self._wrapInQuotes(evt.message)
        if evt.summary:
            evt.summary = self._wrapInQuotes(evt.summary)
        return evt

    def _wrapInQuotes(self, msg):
        """
        Wraps the message in quotes, escaping any existing quote.

        Before:  How do you pronounce "Zenoss"?
        After:  "How do you pronounce \"Zenoss\"?"
        """
        QUOTE = '"'
        BACKSLASH = '\\'
        return ''.join((QUOTE, msg.replace(QUOTE, BACKSLASH + QUOTE), QUOTE))

    def updateContent(self, content=None, data=None):
        super(JIRAReporter, self).updateContent(content, data)

        updates = dict()
        properties = [
            'jira_instance', 'jira_user', 'jira_password',
            'issue_project', 'issue_type', 'issue_priority_key',
            'issue_summary', 'issue_description', 'clear_issue_summary',
            'customfield_keypairs', 'event_rawdata', 'service_group_root'
        ]

        for k in properties:
            updates[k] = data.get(k)

        content.update(updates)

# jira client methods

    def connectJIRA(self, URL, user, password):
        log.debug('[research] : connecting to %s' % (URL))

        basicauth = (user, password)

        try:
            self.jira = JIRA(
                options = {'server' : URL},
                basic_auth = basicauth
            )
            self.connected = True
            log.debug('[research] : connected to %s' % (URL))
        except JIRAError as jx:
            log.error('[research] jira.error : %s' % (jx))
        except Exception as ex:
            log.debug('[research] exception : %s' % (ex))
        finally:
            log.debug('[research] connection info (%s)' % (URL))

    def setIssueValues(self, data, targetValues, issueValues):
        log.debug('[research] process issue values')

        if ('project' in targetValues):
            issueValues['project'] = {
                'key' : targetValues['project']
            }
        if ('issuetype' in targetValues):
            issueValues['issuetype'] = {
                'name' : targetValues['issuetype']
            }

        issueValues['summary'] = self.processEventFields(
            data, issueValues['summary'], 'Summary'
        )
        issueValues['description'] = self.processEventFields(
            data, issueValues['description'], 'Description'
        )

        log.debug('[research] issue values : %s' % (issueValues))

        return issueValues

    def setCustomFieldValues(self, data, targetValues, issueValues):
        log.debug('[research] process customfield values')

        customfields = None
        if ('customfields' in issueValues):
            customfields = issueValues['customfields']
            del issueValues['customfields']

        if (customfields):
            customfields = json.loads(customfields)
        else:
            customfields = {}

        if ('priority' in targetValues):
            customfields['Priority'] = targetValues['priority']

        if ('serviceroot' in targetValues):
            customfields['Service'] = targetValues['serviceroot'] 

        if ('eventraw' in issueValues):
            customfields['Zenoss EventRAW'] = self.processEventFields(
                data, issueValues['eventraw'], 'Event Raw Data'
            )
            del issueValues['eventraw']

        customfields = self.setZenossFields(data, customfields)

        log.debug('[research] customfield values : %s' % (customfields))

        createmeta = self.jira.createmeta(
            projectKeys = targetValues['project'],
            issuetypeNames = targetValues['issuetype'],
            expand = 'projects.issuetypes.fields'
        )

        issuetype = None
        fields = None
        if (createmeta):
            if ('projects' in createmeta):
                if ('issuetypes' in createmeta['projects'][0]):
                    issuetype = createmeta['projects'][0]['issuetypes'][0]
                    log.debug('[research] createmeta issuetype : available')
            if (issuetype):
                if ('fields' in issuetype):
                    fields = issuetype['fields']
                    log.debug('[research] createmeta fields : available')
        else:
            log.debug('[research] createmeta : NOT AVAILABLE')

        if (fields):
            for fKey, fAttr in fields.iteritems():
                if ('name' in fAttr):
                    if (fAttr['name'] in customfields):
                        log.debug('[research] customfield found')
                        fieldValue = customfields[fAttr['name']]
                        if ('allowedValues' in fAttr):
                            log.debug('[research] has customfield options')
                            fieldValue = self.getCustomFieldOption(
                                fAttr['allowedValues'], fieldValue
                            )
                        if (fieldValue):
                            log.debug('[research] cf (%s) set to %s' % (
                                fAttr['name'], fieldValue)
                            )
                            try:
                                if (fAttr['schema']['type'] in ['array']):
                                    fieldValue = [fieldValue]
                            except:
                                pass
                            issueValues[fKey] = fieldValue

        log.debug('[research] issue customfields : %s' % (issueValues))

        return issueValues

    def setZenossFields(self, data, customfields):
        log.debug('[research] process customfield values')

        if (not customfields):
            customfields = {}

        zEventID = self.getEventID(data)
        if (zEventID):
            customfields['Zenoss ID'] = zEventID

        zDeviceID = self.getDeviceID(data)
        if (zDeviceID):
            customfields['Zenoss DevID'] = zDeviceID

        zBaseURL = self.getBaseURL(data) 
        if (zBaseURL):
            customfields['Zenoss Instance'] = zBaseURL

        zEnv = self.getEnvironment(data)
        if (zEnv):
            customfields['Environment'] = zEnv 

        if ('Service' in customfields):
            zSrvc = self.getServiceGroup(data, customfields['Service'])
            if (zSrvc):
                customfields['Service'] = zSrvc

        zLoc = self.getLocation(data)
        if (zLoc):
            customfields['DataCenter'] = zLoc

        log.debug('[research] Zenoss customfields : %s' % (customfields))

        return customfields 

    def createEventIssue(self, data, targetValues, issueValues):
        log.debug('[research] create event issue')

        project = targetValues['project']

        eventID = self.getEventID(data)
        baseHost = self.getBaseHost(data) 
        deviceID = self.getDeviceID(data)

        hasIssues = self.hasEventIssues(project, baseHost, eventID)

        if (hasIssues):
            log.warn('[research] issue exists for EventID %s' % (eventID))
        else:
            issueValues = self.setIssueValues(
                data, targetValues, issueValues
            )
            issueValues = self.setCustomFieldValues(
                data, targetValues, issueValues
            )

            newissue = self.jira.create_issue(fields = issueValues)
            log.info('[research] issue created : %s' % (newissue.key))

    def clearEventIssue(self, data, targetValues, issueValues):
        log.debug('[research] clear event issue')

        project = targetValues['project']

        eventID = self.getEventID(data)
        baseHost = self.getBaseHost(data) 

        issues = self.getEventIssues(project, baseHost, eventID)

        if (not issues):
            log.warn('[research] no issue mapped to clear : %s' % (eventID))
            return

        issueValues = self.setIssueValues(
            data, targetValues, issueValues
        )

        description = issueValues['description']

        eventCLR = self.getEventClearDate(data)

        for issue in issues:
            zenossCLR = self.getCustomFieldID(issue, 'Zenoss EventCLR')
            issuekey = issue.key
            if (zenossCLR):
                issue.update(fields = {zenossCLR : eventCLR})
                log.info('[research] EventCLR updated : %s' % (issuekey))
            if (description):
                self.jira.add_comment(issue.key, description)
                log.info('[research] EventCLR commented : %s' % (issuekey))

    def hasEventIssues(self, project, eventINS, eventID):
        log.debug('[research] has event issues')

        issues = self.getEventIssues(project, eventINS, eventID)

        log.debug('[research] has event issues : %s' % (len(issues) > 0))

        return (len(issues) > 0)

    def getEventIssues(self, project, eventINS, eventID):
        log.debug('[research] get event issues')

        issues = []

        if (eventID):
            issueFilter = '(project = "%s")'
            issueFilter += ' and ("Zenoss Instance" ~ "%s")'
            issueFilter += ' and ("Zenoss ID" ~ "%s")'
            issueFilter = issueFilter % (project, eventINS, eventID)
            log.debug('[research] event issue filter : %s' % (issueFilter))

            try:
                issues = self.jira.search_issues(issueFilter)
                log.debug('[research] event issues : %s' % (len(issues)))
            except JIRAError as jx:
                log.error('[research] jira.error : %s' % (jx))
            except Exception as ex:
                log.error('[research] exception : %s' % (ex))

        return issues

    def getCustomFieldOption(
        self, fieldOptions, value, defaultValue = '',
        exactMatch = False, firstMatch = False):
        log.debug('[research] get customfield options')

        if (not value):
            return None

        if (not fieldOptions):
            return None 

        bDefault = False
        matchValue = None

        if (value.__class__.__name__ in ('str', 'unicode')):
            value = value.split(';')
            if (len(value) > 1):
                defaultValue = value[1].strip()
                log.debug('[research] option default : %s' % (defaultValue))
            value = value[0].strip()

        if (not value):
            log.debug('[research] invalid option value : %s' % (value))

        for av in fieldOptions:
            if ('value' in av):
                valueName = av['value']
            elif ('name' in av):
                valueName = av['name']
            else:
                continue

            if (value):
                if (value.__class__.__name__ in ('str', 'unicode')):
                    if (exactMatch):
                        value = '^%s$' % (value)
                    if (re.match(value, valueName, re.IGNORECASE)):
                        if ('id' in av):
                            matchValue = {'id' : av['id']}
                        else:
                            matchValue = valueName
                        if (firstMatch):
                            break

            if (not defaultValue):
                continue

            if (defaultValue.__class__.__name__ in ('str', 'unicode')):
                if (re.match(defaultValue, valueName, re.IGNORECASE)):
                    bDefault = True
                    if ('id' in av):
                        defaultValue = {'id' : av['id']}
                    else:
                        defaultValue = valueName
                    if (not value):
                        break

        if (not matchValue):
            if (bDefault):
                log.debug('[research] default option : %s' % (defaultValue))
                matchValue = defaultValue

        return matchValue

    def getCustomFieldID(self, issue, fieldName):
        log.debug('[research] get issue customfield ID')

        fieldID = ''

        for field in self.jira.fields():
            if (field['name'].lower() == fieldName.lower()):
                log.debug('[research] customfield matched %s' % (fieldName))
                fieldID = field['id']
                break
        
        return fieldID

    def getEventID(self, data):
        log.debug('[research] get eventID')

        eventID = '${evt/evid}'
        try:
            eventID = self.processEventFields(data, eventID, 'eventID')
        except Exception:
            eventID = ''

        return eventID 

    def getEventClearDate(self, data):
        log.debug('[research] get event clear date')

        eventCLR = '${evt/stateChange}'
        try:
            eventCLR = self.processEventFields(data, eventCLR, 'clear date')
        except Exception:
            eventCLR = ''

        if (eventCLR):
            try:
                eventCLR = datatime.strptime(
                    eventCLR, '%Y-%m-%d %H:%M:%S'
                ).isoformat()[:19] + '.000+0000'
            except:
                try:
                    eventCLR = datatime.strptime(
                        eventCLR, '%Y-%m-%d %H:%M:%S.%f'
                    ).isoformat()[:19] + '.000+0000'
                except:
                    eventCLR = ''

        if (not eventCLR):
            eventCLR = datetime.now().isoformat()[:19] + '.000+0000'

        return eventCLR

    def getDeviceID(self, data):
        log.debug('[research] get deviceID')

        deviceID = '${evt/device}'
        try:
            deviceID = self.processEventFields(data, deviceID, 'deviceID')
        except Exception:
            deviceID = ''

        return deviceID 

    def getBaseURL(self, data):
        log.debug('[research] get baseURL')

        baseURL = '${urls/baseUrl}'
        try:
            baseURL = self.processEventFields(data, baseURL, 'baseURL')
        except Exception:
            baseURL = ''

        if (baseURL):
            baseURL = self.getSiteURI(baseURL)

        return baseURL

    def getBaseHost(self, data):
        log.debug('[research] get baseHost')

        baseHost = ''

        baseHost = self.getBaseURL(data)

        return urlparse(baseHost).hostname

    def getEnvironment(self, data):
        log.debug('[research] get environment')

        eventENV = '${dev/getProductionStateString}'
        try:
            eventENV = self.processEventFields(
                data, eventENV, 'Event ENV (dev)'
            )
        except Exception:
            eventENV = '${evt/prodState}'
            try:
                eventENV = self.processEventFields(
                    data, eventENV, 'Event ENV (evt)'
                )
            except Exception:
                eventENV = ''

        return eventENV

    def getServiceGroup(self, data, valuePattern):
        log.debug('[research] get service group')

        srvcGRP = '${evt/DeviceGroups}'
        try:
            srvcGRP = self.processEventFields(data, srvcGRP, 'Service')
            srvcGRP = srvcGRP.split('|')
        except Exception:
            srvcGRP = []

        extendGRP = []
        defaultGRP = None

        valuePattern = valuePattern.split(';')
        if (len(valuePattern) > 1):
            defaultGRP = valuePattern[1].strip()
        valuePattern = valuePattern[0].strip()

        if (valuePattern):
            for ix in range(len(srvcGRP)):
                svcm = re.match(valuePattern, srvcGRP[ix], re.IGNORECASE)
                if (svcm):
                    valGRP = svcm.group(2)
                    if (valGRP):
                        valGRP = valGRP.split('/')
                        for ex in range(len(valGRP)):
                            extendGRP.append(
                                '\(' + '/'.join(valGRP[:ex + 1]) + '\)'
                            )

        log.debug('[research] service group patterns : %s' % (extendGRP))

        if (extendGRP):
            srvcGRP = '.*(' + '|'.join(extendGRP) + ').*'
        else:
            srvcGRP = ''

        if (defaultGRP):
            srvcGRP += ';' + defaultGRP

        log.debug('[research] service pattern : %s' % (srvcGRP))

        return srvcGRP

    def getLocation(self, data):
        log.debug('[research] get location')

        loc = '${evt/Location}'
        try:
            loc = self.processEventFields(data, loc, 'Location')
        except Exception:
            loc = ''

        for locx in loc.split('/'):
            if (locx):
                return locx

        return loc

    def getSiteURI(self, source):
        outURI = re.findall("((http|https)://[a-zA-Z0-9-\.:]*)", source)
        if (outURI.__class__.__name__ in ['list']):
            if (len(outURI) > 0):
                if (len(outURI[0]) > 0):
                    outURI = outURI[0][0]
        log.debug('[research] zenoss URL : %s' % (outURI)) 
        outURI = urlparse(source)
        return "%s://%s" % (outURI.scheme, outURI.netloc)

    def processEventFields(self, data, content, name):
        log.debug('[research] process TAL expressions')

        try:
            content = processTalSource(content, **data)
            log.debug('[research] %s : %s' % (name, content))
        except Exception:
            log.debug('[research] unable to process : %s' % (name))
            raise ActionExecutionException(
                '[research] failed to process TAL in %s' % (name))

        if (content == 'None'):
            content = ''

        return content

    def removeEmptyListElements(self, listObj):
        log.debug('[research] remove empty list elements')

        bDirty = True
        for lx in range(len(listObj)): 
            try:
                ix = listObj.index('')
                listObj[ix:ix + 1] = []
            except Exception:
                bDirty = False

            try:
                ix = listObj.index()
                listObj[ix:ix + 1] = []
                if (not bDirty):
                    bDirty = True
            except Exception:
                if (not bDirty):
                    bDirty = False

            if (not bDirty):
                break

        return listObj

    def processServiceGroupUsingRoot(self, serviceGroups, rootPattern):
        log.debug('[research] filter service group values')

        return serviceGroups
Esempio n. 11
0
class JiraAlerter(Alerter):
    """ Creates a Jira ticket for each alert """
    required_options = frozenset(['jira_server', 'jira_account_file', 'jira_project', 'jira_issuetype'])

    def __init__(self, rule):
        super(JiraAlerter, self).__init__(rule)
        self.server = self.rule['jira_server']
        self.get_jira_account(self.rule['jira_account_file'])
        self.project = self.rule['jira_project']
        self.issue_type = self.rule['jira_issuetype']
        self.component = self.rule.get('jira_component')
        self.label = self.rule.get('jira_label')
        self.assignee = self.rule.get('jira_assignee')
        self.max_age = self.rule.get('jira_max_age', 30)
        self.bump_tickets = self.rule.get('jira_bump_tickets', False)

        self.jira_args = {'project': {'key': self.project},
                          'issuetype': {'name': self.issue_type}}

        if self.component:
            self.jira_args['components'] = [{'name': self.component}]
        if self.label:
            self.jira_args['labels'] = [self.label]
        if self.assignee:
            self.jira_args['assignee'] = {'name': self.assignee}

        try:
            self.client = JIRA(self.server, basic_auth=(self.user, self.password))
        except JIRAError as e:
            # JIRAError may contain HTML, pass along only first 1024 chars
            raise EAException("Error connecting to JIRA: %s" % (str(e)[:1024]))

    def set_assignee(self, assignee):
        self.assignee = assignee
        if assignee:
            self.jira_args['assignee'] = {'name': assignee}
        elif 'assignee' in self.jira_args:
            self.jira_args.pop('assignee')

    def get_jira_account(self, account_file):
        """ Gets the username and password from a jira account file.

        :param account_file: Name of the file which contains user and password information.
        """
        account_conf = yaml_loader(account_file)
        if 'user' not in account_conf or 'password' not in account_conf:
            raise EAException('Jira account file must have user and password fields')
        self.user = account_conf['user']
        self.password = account_conf['password']

    def find_existing_ticket(self, matches):
        # Default title, get stripped search version
        if 'alert_subject' not in self.rule:
            title = self.create_default_title(matches, True)
        else:
            title = self.create_title(matches)

        # This is necessary for search for work. Other special characters and dashes
        # directly adjacent to words appear to be ok
        title = title.replace(' - ', ' ')

        date = (datetime.datetime.now() - datetime.timedelta(days=self.max_age)).strftime('%Y/%m/%d')
        jql = 'project=%s AND summary~"%s" and created >= "%s"' % (self.project, title, date)
        try:
            issues = self.client.search_issues(jql)
        except JIRAError as e:
            logging.exception("Error while searching for JIRA ticket using jql '%s': %s" % (jql, e))
            return None

        if len(issues):
            return issues[0]

    def comment_on_ticket(self, ticket, match):
        text = str(JiraFormattedMatchString(self.rule, match))
        timestamp = pretty_ts(match[self.rule['timestamp_field']])
        comment = "This alert was triggered again at %s\n%s" % (timestamp, text)
        self.client.add_comment(ticket, comment)

    def alert(self, matches):
        title = self.create_title(matches)

        if self.bump_tickets:
            ticket = self.find_existing_ticket(matches)
            if ticket:
                logging.info('Commenting on existing ticket %s' % (ticket.key))
                for match in matches:
                    self.comment_on_ticket(ticket, match)
                if self.pipeline is not None:
                    self.pipeline['jira_ticket'] = ticket
                return

        description = ''
        for match in matches:
            description += str(JiraFormattedMatchString(self.rule, match))
            if len(matches) > 1:
                description += '\n----------------------------------------\n'

        self.jira_args['summary'] = title
        self.jira_args['description'] = description

        try:
            self.issue = self.client.create_issue(**self.jira_args)
        except JIRAError as e:
            raise EAException("Error creating JIRA ticket: %s" % (e))
        logging.info("Opened Jira ticket: %s" % (self.issue))

        if self.pipeline is not None:
            self.pipeline['jira_ticket'] = self.issue

    def create_default_title(self, matches, for_search=False):
        # If there is a query_key, use that in the title
        if 'query_key' in self.rule and self.rule['query_key'] in matches[0]:
            title = 'ElastAlert: %s matched %s' % (matches[0][self.rule['query_key']], self.rule['name'])
        else:
            title = 'ElastAlert: %s' % (self.rule['name'])

        if for_search:
            return title

        title += ' - %s' % (pretty_ts(matches[0][self.rule['timestamp_field']], self.rule.get('use_local_time')))

        # Add count for spikes
        count = matches[0].get('spike_count')
        if count:
            title += ' - %s+ events' % (count)

        return title

    def get_info(self):
        return {'type': 'jira'}
Esempio n. 12
0
class JiraAlerter(Alerter):
    """ Creates a Jira ticket for each alert """
    required_options = frozenset(
        ['jira_server', 'jira_account_file', 'jira_project', 'jira_issuetype'])

    # Maintain a static set of built-in fields that we explicitly know how to set
    # For anything else, we will do best-effort and try to set a string value
    known_field_list = [
        'jira_account_file',
        'jira_assignee',
        'jira_bump_in_statuses',
        'jira_bump_not_in_statuses',
        'jira_bump_tickets',
        'jira_component',
        'jira_components',
        'jira_description',
        'jira_ignore_in_title',
        'jira_issuetype',
        'jira_label',
        'jira_labels',
        'jira_max_age',
        'jira_priority',
        'jira_project',
        'jira_server',
        'jira_watchers',
    ]

    # Some built-in jira types that can be used as custom fields require special handling
    # Here is a sample of one of them:
    # {"id":"customfield_12807","name":"My Custom Field","custom":true,"orderable":true,"navigable":true,"searchable":true,
    # "clauseNames":["cf[12807]","My Custom Field"],"schema":{"type":"array","items":"string",
    # "custom":"com.atlassian.jira.plugin.system.customfieldtypes:multiselect","customId":12807}}
    # There are likely others that will need to be updated on a case-by-case basis
    custom_string_types_with_special_handling = [
        'com.atlassian.jira.plugin.system.customfieldtypes:multicheckboxes',
        'com.atlassian.jira.plugin.system.customfieldtypes:multiselect',
        'com.atlassian.jira.plugin.system.customfieldtypes:radiobuttons',
    ]

    def __init__(self, rule):
        super(JiraAlerter, self).__init__(rule)
        self.server = self.rule['jira_server']
        self.get_account(self.rule['jira_account_file'])
        self.project = self.rule['jira_project']
        self.issue_type = self.rule['jira_issuetype']

        # We used to support only a single component. This allows us to maintain backwards compatibility
        # while also giving the user-facing API a more representative name
        self.components = self.rule.get('jira_components',
                                        self.rule.get('jira_component'))

        # We used to support only a single label. This allows us to maintain backwards compatibility
        # while also giving the user-facing API a more representative name
        self.labels = self.rule.get('jira_labels', self.rule.get('jira_label'))

        self.description = self.rule.get('jira_description', '')
        self.assignee = self.rule.get('jira_assignee')
        self.max_age = self.rule.get('jira_max_age', 30)
        self.priority = self.rule.get('jira_priority')
        self.bump_tickets = self.rule.get('jira_bump_tickets', False)
        self.bump_not_in_statuses = self.rule.get('jira_bump_not_in_statuses')
        self.bump_in_statuses = self.rule.get('jira_bump_in_statuses')
        self.watchers = self.rule.get('jira_watchers')

        if self.bump_in_statuses and self.bump_not_in_statuses:
            msg = 'Both jira_bump_in_statuses (%s) and jira_bump_not_in_statuses (%s) are set.' % \
                  (','.join(self.bump_in_statuses), ','.join(self.bump_not_in_statuses))
            intersection = list(
                set(self.bump_in_statuses) & set(self.bump_in_statuses))
            if intersection:
                msg = '%s Both have common statuses of (%s). As such, no tickets will ever be found.' % (
                    msg, ','.join(intersection))
            msg += ' This should be simplified to use only one or the other.'
            logging.warning(msg)

        self.jira_args = {
            'project': {
                'key': self.project
            },
            'issuetype': {
                'name': self.issue_type
            }
        }

        if self.components:
            # Support single component or list
            if type(self.components) != list:
                self.jira_args['components'] = [{'name': self.components}]
            else:
                self.jira_args['components'] = [{
                    'name': component
                } for component in self.components]
        if self.labels:
            # Support single label or list
            if type(self.labels) != list:
                self.labels = [self.labels]
            self.jira_args['labels'] = self.labels
        if self.watchers:
            # Support single watcher or list
            if type(self.watchers) != list:
                self.watchers = [self.watchers]
        if self.assignee:
            self.jira_args['assignee'] = {'name': self.assignee}

        try:
            self.client = JIRA(self.server,
                               basic_auth=(self.user, self.password))
            self.get_priorities()
            self.get_arbitrary_fields()
        except JIRAError as e:
            # JIRAError may contain HTML, pass along only first 1024 chars
            raise EAException("Error connecting to JIRA: %s" % (str(e)[:1024]))

        try:
            if self.priority is not None:
                self.jira_args['priority'] = {
                    'id': self.priority_ids[self.priority]
                }
        except KeyError:
            logging.error("Priority %s not found. Valid priorities are %s" %
                          (self.priority, self.priority_ids.keys()))

    def get_arbitrary_fields(self):
        # This API returns metadata about all the fields defined on the jira server (built-ins and custom ones)
        fields = self.client.fields()
        for jira_field, value in self.rule.iteritems():
            # If we find a field that is not covered by the set that we are aware of, it means it is either:
            # 1. A built-in supported field in JIRA that we don't have on our radar
            # 2. A custom field that a JIRA admin has configured
            if jira_field.startswith(
                    'jira_') and jira_field not in self.known_field_list:
                # Remove the jira_ part.  Convert underscores to spaces
                normalized_jira_field = jira_field[5:].replace('_',
                                                               ' ').lower()
                # All jira fields should be found in the 'id' or the 'name' field. Therefore, try both just in case
                for identifier in ['name', 'id']:
                    field = next((f for f in fields if normalized_jira_field ==
                                  f[identifier].replace('_', ' ').lower()),
                                 None)
                    if field:
                        break
                if not field:
                    # Log a warning to ElastAlert saying that we couldn't find that type?
                    # OR raise and fail to load the alert entirely? Probably the latter...
                    raise Exception(
                        "Could not find a definition for the jira field '{0}'".
                        format(normalized_jira_field))
                arg_name = field['id']
                # Check the schema information to decide how to set the value correctly
                # If the schema information is not available, raise an exception since we don't know how to set it
                # Note this is only the case for two built-in types, id: issuekey and id: thumbnail
                if not ('schema' in field or 'type' in field['schema']):
                    raise Exception(
                        "Could not determine schema information for the jira field '{0}'"
                        .format(normalized_jira_field))
                arg_type = field['schema']['type']

                # Handle arrays of simple types like strings or numbers
                if arg_type == 'array':
                    # As a convenience, support the scenario wherein the user only provides
                    # a single value for a multi-value field e.g. jira_labels: Only_One_Label
                    if type(value) != list:
                        value = [value]
                    array_items = field['schema']['items']
                    # Simple string types
                    if array_items in ['string', 'date', 'datetime']:
                        # Special case for multi-select custom types (the JIRA metadata says that these are strings, but
                        # in reality, they are required to be provided as an object.
                        if 'custom' in field['schema'] and field['schema'][
                                'custom'] in self.custom_string_types_with_special_handling:
                            self.jira_args[arg_name] = [{
                                'value': v
                            } for v in value]
                        else:
                            self.jira_args[arg_name] = value
                    elif array_items == 'number':
                        self.jira_args[arg_name] = [int(v) for v in value]
                    # Also attempt to handle arrays of complex types that have to be passed as objects with an identifier 'key'
                    else:
                        # Try setting it as an object, using 'name' as the key
                        # This may not work, as the key might actually be 'key', 'id', 'value', or something else
                        # If it works, great!  If not, it will manifest itself as an API error that will bubble up
                        self.jira_args[arg_name] = [{'name': v} for v in value]
                # Handle non-array types
                else:
                    # Simple string types
                    if arg_type in ['string', 'date', 'datetime']:
                        # Special case for custom types (the JIRA metadata says that these are strings, but
                        # in reality, they are required to be provided as an object.
                        if 'custom' in field['schema'] and field['schema'][
                                'custom'] in self.custom_string_types_with_special_handling:
                            self.jira_args[arg_name] = {'value': value}
                        else:
                            self.jira_args[arg_name] = value
                    # Number type
                    elif arg_type == 'number':
                        self.jira_args[arg_name] = int(value)
                    # Complex type
                    else:
                        self.jira_args[arg_name] = {'name': value}

    def get_priorities(self):
        """ Creates a mapping of priority index to id. """
        priorities = self.client.priorities()
        self.priority_ids = {}
        for x in range(len(priorities)):
            self.priority_ids[x] = priorities[x].id

    def set_assignee(self, assignee):
        self.assignee = assignee
        if assignee:
            self.jira_args['assignee'] = {'name': assignee}
        elif 'assignee' in self.jira_args:
            self.jira_args.pop('assignee')

    def find_existing_ticket(self, matches):
        # Default title, get stripped search version
        if 'alert_subject' not in self.rule:
            title = self.create_default_title(matches, True)
        else:
            title = self.create_title(matches)

        if 'jira_ignore_in_title' in self.rule:
            title = title.replace(
                matches[0].get(self.rule['jira_ignore_in_title'], ''), '')

        # This is necessary for search to work. Other special characters and dashes
        # directly adjacent to words appear to be ok
        title = title.replace(' - ', ' ')
        title = title.replace('\\', '\\\\')

        date = (datetime.datetime.now() -
                datetime.timedelta(days=self.max_age)).strftime('%Y-%m-%d')
        jql = 'project=%s AND summary~"%s" and created >= "%s"' % (
            self.project, title, date)
        if self.bump_in_statuses:
            jql = '%s and status in (%s)' % (jql, ','.join(
                self.bump_in_statuses))
        if self.bump_not_in_statuses:
            jql = '%s and status not in (%s)' % (jql, ','.join(
                self.bump_not_in_statuses))
        try:
            issues = self.client.search_issues(jql)
        except JIRAError as e:
            logging.exception(
                "Error while searching for JIRA ticket using jql '%s': %s" %
                (jql, e))
            return None

        if len(issues):
            return issues[0]

    def comment_on_ticket(self, ticket, match):
        text = unicode(JiraFormattedMatchString(self.rule, match))
        timestamp = pretty_ts(
            lookup_es_key(match, self.rule['timestamp_field']))
        comment = "This alert was triggered again at %s\n%s" % (timestamp,
                                                                text)
        self.client.add_comment(ticket, comment)

    def alert(self, matches):
        title = self.create_title(matches)

        if self.bump_tickets:
            ticket = self.find_existing_ticket(matches)
            if ticket:
                elastalert_logger.info('Commenting on existing ticket %s' %
                                       (ticket.key))
                for match in matches:
                    try:
                        self.comment_on_ticket(ticket, match)
                    except JIRAError as e:
                        logging.exception(
                            "Error while commenting on ticket %s: %s" %
                            (ticket, e))
                if self.pipeline is not None:
                    self.pipeline['jira_ticket'] = ticket
                    self.pipeline['jira_server'] = self.server
                return None

        self.jira_args['summary'] = title
        self.jira_args['description'] = self.create_alert_body(matches)

        try:
            self.issue = self.client.create_issue(**self.jira_args)

            # You can not add watchers on initial creation. Only as a follow-up action
            if self.watchers:
                for watcher in self.watchers:
                    try:
                        self.client.add_watcher(self.issue.key, watcher)
                    except Exception as ex:
                        # Re-raise the exception, preserve the stack-trace, and give some
                        # context as to which watcher failed to be added
                        raise Exception(
                            "Exception encountered when trying to add '{0}' as a watcher. Does the user exist?\n{1}"
                            .format(watcher, ex)), None, sys.exc_info()[2]

        except JIRAError as e:
            raise EAException("Error creating JIRA ticket: %s" % (e))
        elastalert_logger.info("Opened Jira ticket: %s" % (self.issue))

        if self.pipeline is not None:
            self.pipeline['jira_ticket'] = self.issue
            self.pipeline['jira_server'] = self.server

    def create_alert_body(self, matches):
        body = self.description + '\n'
        for match in matches:
            body += unicode(JiraFormattedMatchString(self.rule, match))
            if len(matches) > 1:
                body += '\n----------------------------------------\n'
        return body

    def create_default_title(self, matches, for_search=False):
        # If there is a query_key, use that in the title
        if 'query_key' in self.rule and self.rule['query_key'] in matches[0]:
            title = 'ElastAlert: %s matched %s' % (
                matches[0][self.rule['query_key']], self.rule['name'])
        else:
            title = 'ElastAlert: %s' % (self.rule['name'])

        if for_search:
            return title

        title += ' - %s' % (pretty_ts(matches[0][self.rule['timestamp_field']],
                                      self.rule.get('use_local_time')))

        # Add count for spikes
        count = matches[0].get('spike_count')
        if count:
            title += ' - %s+ events' % (count)

        return title

    def get_info(self):
        return {'type': 'jira'}
Esempio n. 13
0
class jirapp():
    """ JIRA++ is JIRA+1. Use it to profit. """
    def __init__(self, args, mongo=None):
        if not isinstance(args, dict):
            args = vars(args)
        self.args = args
        self.live = False

        logging.basicConfig(format='%(asctime)s - %(module)s - %(levelname)s -'
                                   ' %(message)s')
        self.logger = logging.getLogger("logger")
        self.logger.setLevel(self.args['log_level'])
        fh = logging.FileHandler(self.args['log'])
        self.logger.addHandler(fh)

        self.logger.info("Initializing JIRA++")
        self.logger.debug(self.args)

        if mongo is None:
            try:
                self.mongo = pymongo.MongoClient(self.args['mongo_uri'])
            except pymongo.errors.PyMongoError as e:
                self.logger.exception(e)
                raise e
        else:
            self.mongo = mongo

        # Initialize dbs and collections
        self.db_jirameta = self.mongo.jirameta

        opts = {'server': 'https://jira.mongodb.org', "verify": False}
        auth = (self.args['jira_username'], self.args['jira_password'])

        try:
            self.jira = JIRA(options=opts, basic_auth=auth)
        except JIRAError as e:
            raise e

    def __getTransitionId(self, key, transition):
        """ This method gets the transition id for the given transition name.
        It is dependent on the JIRA issue project and status """
        # A ticket may undergo several state-changing actions between the time
        # we first queried it in our local db and now. Until we come up with
        # something foolproof we'll query JIRA each time for the ticket status
        # before performing the transition. It's annoying but that's life dude
        try:
            issue = self.jira.issue(key)
        except JIRAError as e:
            return {'ok': False, 'payload': e}

        project = issue.fields.project.key
        status = issue.fields.status.name

        # transition id
        tid = None

        self.logger.debug("Finding %s transition id for project:'%s', "
                          "status:'%s'" % (transition, project, status))

        try:
            coll_transitions = self.db_jirameta.transitions
            match = {'pkey': project, 'sname': status, 'tname': transition}
            proj = {'tid': 1, '_id': 0}
            doc = coll_transitions.find_one(match, proj)
        except pymongo.errors.PyMongoError as e:
            return {'ok': False, 'payload': e}

        if doc and 'tid' in doc and doc['tid'] is not None:
            tid = doc['tid']
            self.logger.info("Found transition id:%s" % tid)
        else:
            self.logger.warning("Transition id not found. Most likely issue is"
                                " already in the desired state.")

        return {'ok': True, 'payload': tid}

    def _getLastUpdated(self, key):
        """ Get the last updated time for the given JIRA issue and return it as
        a datetime """
        try:
            issue = self.jira.issue(key)
        except JIRAError as e:
            self.logger.exception(e)
            return None

        try:
            dt = datetime.datetime.strptime(issue.fields.updated,
                                            '%Y-%m-%dT%H:%M:%S.%f+0000')
        except Exception as e:
            self.logger.exception(e)
            return None
        return dt

    def addPublicComment(self, key, comment):
        """ This method adds a public-facing comment to a JIRA issue """
        # TODO validate comment
        self.logger.info("Adding public comment to %s" % key)

        if self.live:
            try:
                self.jira.add_comment(key, comment)
            except JIRAError as e:
                return {'ok': False, 'payload': e}

        # Payload to include the issue key and last updated time
        updated = self._getLastUpdated(key)
        return {'ok': True, 'payload': {'key': key, 'updated': updated}}

    def addDeveloperComment(self, key, comment):
        """ This method adds a developer-only comment to a JIRA issue """
        # TODO validate comment
        self.logger.info("Adding developer-only comment to %s" % key)

        if self.live:
            try:
                self.jira.add_comment(key, comment, visibility={'type': 'role',
                                      'value': 'Developers'})
            except JIRAError as e:
                return {'ok': False, 'payload': e}

        updated = self._getLastUpdated(key)
        return {'ok': True, 'payload': {'key': key, 'updated': updated}}

    def closeIssue(self, key):
        """ This method closes a JIRA issue """
        self.logger.info("Closing %s" % key)

        if self.live:
            res = self.__getTransitionId(key, 'Close Issue')
            if not res['ok']:
                return res
            tid = res['payload']
            if tid:
                try:
                    self.jira.transition_issue(key, tid)
                except JIRAError as e:
                    return {'ok': False, 'payload': e}

        updated = self._getLastUpdated(key)
        return {'ok': True, 'payload': {'key': key, 'updated': updated}}

    def createIssue(self, fields={}):
        """ This method creates a JIRA issue. Assume fields is in a format that
        can be passed to JIRA.create_issue, i.e. use SupportIssue.getJIRAFields
        """
        # Use createmeta to identify required fields for ticket creation
        if 'project' not in fields or 'issuetype' not in fields:
            return {'ok': False, 'payload': 'project and issuetype required '
                                            'for createmeta'}

        coll_createmeta = self.db_jirameta.createmeta

        match = {'pkey': fields['project']['key'], 'itname':
                 fields['issuetype']['name']}
        proj = {'required': 1, '_id': 0}

        # required fields for issue creation
        required_fields = None

        self.logger.info("Getting createmeta data for project:%s, issuetype:%s"
                         % (fields['project']['key'],
                            fields['issuetype']['name']))

        try:
            doc = coll_createmeta.find_one(match, proj)
        except pymongo.errors.PyMongoError as e:
            return {'ok': False, 'payload': e}

        if doc and 'required' in doc:
            required_fields = doc['required']

        if required_fields is not None:
            for f in required_fields:
                if f not in fields:
                    message = "%s required to create JIRA issue of type '%s' "\
                              "in project '%s'" % (f,
                                                   fields['issuetype']['name'],
                                                   fields['project']['key'])
                    self.logger.warning(message)
                    return {'ok': False, 'payload': message}

        self.logger.info("Creating JIRA issue...")

        if self.live:
            try:
                issue = self.jira.create_issue(fields=fields)
                self.logger.info("Created %s" % issue.key)
                return {'ok': True, 'payload': issue.key}
            except JIRAError as e:
                return {'ok': False, 'payload': e}
        else:
            self.logger.debug(fields)
            return {'ok': True,
                    'payload': '%s-XXXXX' % fields['project']['key']}

    def _healthcheck(self, **kwargs):
        """ Perform sanity checks """
        isOk = True
        messages = []
        # Can we access jira?
        try:
            self.jira.server_info()
        except JIRAError as e:
            self.logger.exception(e)
            isOk = False
            messages.append("jirapp: unable to get JIRA server info")
        # Can we read from jirameta?
        try:
            self.db_jirameta.transitions.find_one({"_id": 1})
            self.db_jirameta.createmeta.find_one({"_id": 1})
        except pymongo.errors.PyMongoError as e:
            self.logger.exception(e)
            isOk = False
            messages.append("jirapp: unable to read from jirameta db: %s" % e)
        return {'ok': isOk, 'payload': messages}

    def resolveIssue(self, key, resolution):
        """ This method resolves a JIRA issue with the given resolution """
        # TODO fetch and cache results of jira.resolutions() elsewhere
        res_map = {'Fixed': '1',
                   "Won't Fix": '2',
                   'Duplicate': '3',
                   'Incomplete': '4',
                   'Cannot Reproduce': '5',
                   'Works as Designed': '6',
                   'Gone away': '7',
                   'Community Answered': '8',
                   'Done': '9'}

        if resolution in res_map:
            rid = res_map[resolution]
        else:
            return {'ok': False, 'payload': "%s is not a supported resolution "
                                            "type" % resolution}

        self.logger.info("Resolving %s" % key)

        if self.live:
            res = self.__getTransitionId(key, 'Resolve Issue')
            if not res['ok']:
                return res
            tid = res['payload']
            if tid:
                try:
                    self.jira.transition_issue(key, tid,
                                               resolution={'id': rid})
                except JIRAError as e:
                    return {'ok': False, 'payload': e}

        updated = self._getLastUpdated(key)
        return {'ok': True, 'payload': {'key': key, 'updated': updated}}

    def _run(self):
        self.logger.debug("run()")
        for key in self.args['key']:
            issue = self.jira.issue(key)
            print("%s: %s" % (issue.key, issue.fields.summary))
        sys.exit(0)

    def setLabels(self, key, labels):
        """ This method sets the labels in a JIRA issue """
        # TODO validate labels is a string that will return [] on split
        self.logger.info("Setting labels in %s" % key)

        try:
            issue = self.jira.issue(key)
        except JIRAError as e:
            return {'ok': False, 'payload': e}

        try:
            issue.update(labels=labels.split(','))
        except JIRAError as e:
            return {'ok': False, 'payload': e}

        updated = self._getLastUpdated(key)
        return {'ok': True, 'payload': {'key': key, 'updated': updated}}

    def _setLive(self, b):
        """ Lock and load? """
        self.live = b

    def setOwner(self, key, owner):
        """ This method sets the JIRA issue owner using the Internal Fields
        transition """
        self.logger.info("Setting owner of %s" % key)

        if self.live:
            fields = {'customfield_10041': {'name': owner}}
            res = self.__getTransitionId(key, 'Internal Fields')
            if not res['ok']:
                return res
            tid = res['payload']

            if tid:
                try:
                    self.jira.transition_issue(key, tid, fields=fields)
                except JIRAError as e:
                    return {'ok': False, 'payload': e}

        updated = self._getLastUpdated(key)
        return {'ok': True, 'payload': {'key': key, 'updated': updated}}

    def wfcIssue(self, key):
        """ This method sets the status of a ticket to Wait for Customer """
        self.logger.info("Setting %s to Wait for Customer" % key)

        if self.live:
            res = self.__getTransitionId(key, 'Wait for Customer')
            if not res['ok']:
                return res
            tid = res['payload']
            if tid:
                try:
                    self.jira.transition_issue(key, tid)
                except JIRAError as e:
                    return {'ok': False, 'payload': e}

        updated = self._getLastUpdated(key)
        return {'ok': True, 'payload': {'key': key, 'updated': updated}}
Esempio n. 14
0
import sys, getopt
from jira.client import JIRA
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-t", "--ticketid", help="Enter TicketID. example: OPS-5432")
parser.add_argument("-c", "--comment",help="Please comment")
args = parser.parse_args()



options = {'server':'https://jira.corp.domain.com','verify':False}

jira = JIRA(options, basic_auth=('anto.daniel','xxxxxxxxxxx'))

ticketid = args.ticketid
addcomment = args.comment

comment = jira.add_comment(ticketid, addcomment)
print 'added comment'
Esempio n. 15
0
githubBranchUrl = 'https://github.com/jabong/' + repositoryName + '/tree/' + branchName
pullRequestUrl = 'https://github.com/jabong/' + repositoryName + '/pull/' + pullRequestId
jiraIssueUrl = jiraServer + '/browse/' + issueKey

strComment = 'h3. {color:green}Code Review @ revision ' + revisionNo + '{color}'
strComment += '\n{color:green}Code is OK @ revision [' + revisionNo + '|' + commitUrl + '] in GitHub{color}'
strComment += '\nBranch @ GitHub: ' + githubBranchUrl
strComment += '\nPull Request can be seen at ' + pullRequestUrl

githubPullRequestCommentBody = '### Code is Ok @ Revision ' + revisionNo
githubPullRequestCommentBody += '\nJira Issue: ' + jiraIssueUrl
githubPullRequestCommentBody += '\nBranch @ GitHub: ' + githubBranchUrl

print "================ Adding Comment to JIRA ====================="
print strComment
jira.add_comment(issue, strComment)
print "================ Added Comment ============================="

print "================ Adding Comment to GitHub Pull Request ====================="
print githubPullRequestCommentBody
oGithubIssue.create_comment(body = githubPullRequestCommentBody)
print "================ Added Comment ============================="

# Verify if the Comment was added successfully
updatedIssue = jira.issue(issueKey)
print "====== Comment Added: ========"
print updatedIssue.fields.comment.comments[updatedIssue.fields.comment.total - 1].body

print "====== Issue Url: ========"
print jiraIssueUrl
# This script shows how do basic tweaks to a JIRA
# issue.

from jira.client import JIRA

# By default, the client will connect to a JIRA instance started from the Atlassian Plugin SDK
# (see https://developer.atlassian.com/display/DOCS/Installing+the+Atlassian+Plugin+SDK for details).
# Override this with the options parameter.
options = {
    'server': 'JIRA URL'
}

jira = JIRA(options, basic_auth=('JIRA USERNAME', 'JIRA PASSWORD'))

# Get an issue.
issue = jira.issue('PUT HERE AN ISSUE IN YOUR SYSTEM')

#issue.update(assignee={'name': ''}) # this is to un-assign an issue
issue.update(assignee={'name': 'NAME OF A USER'})
jira.add_comment('ISSUE ID', 'new comment')
Esempio n. 17
0
class Housekeeping():
    """
    This class is the container for all automated Jira functions performed
    by the Housekeeping agent.
    
    """
    def __init__(self):
        # class variables
        self.ac_label = u'auto-close-24-hours'
        self.audit_delay = '-72h'
        self.audit_projects = "INDEXREP"  #comma delimited project keys
        # open JIRA API Connection
        self.jira = JIRA(options=secrets.options,
                         basic_auth=secrets.housekeeping_auth)

        # commands to run
        self.content_acquisition_auto_qc()
        self.auto_assign()
        self.remind_reporter_to_close()
        self.close_resolved()
        self.clear_auto_close_label()
        self.resolved_issue_audit()
        self.handle_audited_tickets()

    def content_acquisition_auto_qc(self):
        """
        Takes INDEXREP issues that have been Merged for 30+ minutes and 
        transitions them to Quality Control. It then adds a comment that
        tags the reporter to inform them that the issue is ready for review.

        """
        issues = self.jira.search_issues(
            'project=INDEXREP and status=Merged and updated<="-30m"')

        for issue in issues:
            reporter = issue.fields.reporter.key
            message = '[~%s], this issue is ready for QC.' % reporter
            """ 
            771 is the transition ID spedific to this step for this project.
            Anything more generic will need to parse the transitions list.
            """
            self.jira.transition_issue(issue.key, '771')
            self.jira.add_comment(issue.key, message)

    def handle_audited_tickets(self):
        """
        Handles audit tickets that are failed. Closed tickets are ignored. Failed 
        tickets trigger the creation of a new ticket in the same project as the 
        original ticket.
        
        Inputs: None
        Returns: None
        
        """
        issues = self.jira.search_issues(  # get all the ADT issues
            'project=ADT and status="Failed Audit"')

        # For each failed issue, generate a new work ticket then close this one
        for issue in issues:
            #BUID
            adt_buid = issue.fields.customfield_10502
            #WCID
            adt_wcid = issue.fields.customfield_10501
            #Indexing Type
            adt_indexing_type = issue.fields.customfield_10500
            #comments
            adt_comments = []
            for comment in self.jira.comments(issue):
                node = {
                    'body': self.jira.comment(issue, comment).body,
                    'author': self.jira.comment(issue, comment).author.key
                }
                adt_comments.append(node)

            link_list = [
                issue.key,
            ]  # first linked ticket should be this audit ticket
            for link in issue.fields.issuelinks:  # grab the rest of links
                try:
                    link_list.append(link.outwardIssue.key)
                except AttributeError:
                    pass

            # capture orignal tick and project
            original_ticket = issue.fields.summary.split("[")[1].split("]")[0]
            original_project = original_ticket.split("-")[0]

            # build the new summary by parsing the audit summary
            indexrep_summary = issue.fields.summary  #build the summary
            indexrep_summary = indexrep_summary.replace(
                "compliance audit - ", "")
            indexrep_summary = indexrep_summary.split("[")[0]
            indexrep_summary = ' %s - Failed Audit' % (indexrep_summary)

            # Build the issue description
            message = 'This issue failed audit. Please review %s and make any \
                necessary corrections.' % original_ticket

            # Construct the watcher list and de-dupe it
            watcher_list = [
                issue.fields.assignee.key,
            ]
            for w in self.jira.watchers(issue).watchers:
                watcher_list.append(w.key)
            watcher_list = set(watcher_list)

            # get the reporter (reporter is preserved from audit to issue)
            reporter = issue.fields.reporter.key

            # Generate the new issue, then close the audit ticket.
            new_issue = self.make_new_issue(original_project, "EMPTY",
                                            reporter, indexrep_summary,
                                            message, watcher_list, link_list,
                                            adt_buid, adt_wcid,
                                            adt_indexing_type, adt_comments)
            close_me = self.close_issue(issue.key)

    def resolved_issue_audit(self, delay="", projects=""):
        """
        TAKES issues that have been resolved from specified projectsfor a set 
        interval and creates a new ticket in AUDIT, closes the INDEXREP ticket, 
        and then assigns it to the audit user specified in the self.qa_auditor role.
        
        Inputs:
        :delay:      how long an issue should be resoved before being picked up
                        by this script. Defaults to class level variable
        :projects:  which projects are subject to auditing. Defaults to class level
                        variable
        Returns:    Error message or Nothing
        
        """
        delay = self.audit_delay if not delay else delay
        projects = self.audit_projects if not projects else projects
        # get all the issues from projects in the audit list
        issue_query = 'project in (%s) and status=Resolved and resolutiondate \
            <="%s"' % (projects, delay)
        issues = self.jira.search_issues(issue_query)

        # get the users who can be assigned audit tickets. This should be just one person
        qa_members = self.get_group_members("issue audits")
        if len(qa_members) == 1:
            qa_auditor = qa_members.keys()[0]
        else:
            # for now, throw an error. Later, assign to user with fewer ADT tickets
            # this will also mean turning the code in auto_assign into a method (DRY)
            return "Error: There is more than one possible auditor"

        # cycle through them and create a new ADT ticket for each
        for issue in issues:
            #BUID
            ind_buid = issue.fields.customfield_10502
            #WCID
            ind_wcid = issue.fields.customfield_10501
            #Indexing Type
            ind_indexing_type = issue.fields.customfield_10500
            link_list = [
                issue.key,
            ]
            for link in issue.fields.issuelinks:  # grab the rest of links
                try:
                    link_list.append(link.outwardIssue.key)
                except AttributeError:
                    pass
            # build the new ticket summary based on the issue being audited
            # [ISSUE=123] is used to preserve the original issue key. Replace any brackets with ()
            # to prevent read errors later.
            adt_summary = issue.fields.summary.replace("[",
                                                       "(").replace("]", ")")
            adt_summary = 'compliance audit - %s [%s]' % (adt_summary,
                                                          issue.key)
            # build the description
            message = '[~%s], issue %s is ready to audit.' % (qa_auditor,
                                                              issue.key)

            #build the watcher list, including original reporter and assignee of the audited ticket
            watcher_list = []
            for w in self.jira.watchers(issue).watchers:
                watcher_list.append(w.key)
            reporter = issue.fields.reporter.key
            try:
                original_assignee = issue.fields.assignee.key
            except AttributeError:
                original_assignee = "EMPTY"

            # make the audit ticket
            new_issue = self.make_new_issue("ADT", qa_auditor, reporter,
                                            adt_summary, message, watcher_list,
                                            link_list, ind_buid, ind_wcid,
                                            ind_indexing_type)

            # close the INDEXREP ticket
            close_me = self.close_issue(issue.key)

            # add comment to indexrep ticket
            link_back_comment = "This issue has been closed. The audit ticket is %s" % new_issue
            self.jira.add_comment(issue.key, link_back_comment)

    def make_new_issue(self,
                       project,
                       issue_assignee,
                       issue_reporter,
                       summary,
                       description="",
                       watchers=[],
                       links=[],
                       buid="",
                       wcid="",
                       indexing_type="",
                       comments=[],
                       issuetype="Task"):
        """
        Creates a new issue with the given parameters.
        Inputs:
        *REQUIRED*
            :project:   the jira project key in which to create the issue
            :issue_assignee:    user name who the issue will be assigned to
            :issue_reporter:    user name of the issue report
            :summary:   string value of the issue summary field
            *OPTIONAL*
            :description: Issue description. Defaults to empty string
            :watchers: list of user names to add as issue watchers
            :link:  list of issue keys to link to the issue as "Related To"
            :issuetype: the type of issue to create. Defaults to type.
            :buid: business unit - custom field 10502
            :wcid: wrapping company id - custom field 10501
            :indexing_type: the indexing type - custom field 10500
            :comments: list dictionaries of comments and authors to auto add.
        Returns: Jira Issue Object
        
        """
        issue_dict = {
            'project': {
                'key': project
            },
            'summary': summary,
            'issuetype': {
                'name': issuetype
            },
            'description': description,
        }
        new_issue = self.jira.create_issue(fields=issue_dict)

        # assign the audit tick to auditor
        new_issue.update(assignee={'name': issue_assignee})
        new_issue.update(reporter={'name': issue_reporter})

        # add watchers to audit ticket (reporter, assignee, wacthers from indexrep ticket)
        for watcher in watchers:
            self.jira.add_watcher(new_issue, watcher)

        # link the audit ticket back to indexrep ticket
        for link in links:
            self.jira.create_issue_link('Relates', new_issue, link)

        # add custom field values if set
        if buid:
            new_issue.update(fields={'customfield_10502': buid})
        if wcid:
            new_issue.update(fields={'customfield_10501': wcid})
        if indexing_type:
            new_issue.update(
                fields={'customfield_10500': {
                    'value': indexing_type.value
                }})

        # add comments
        quoted_comments = ""
        for comment in comments:
            quoted_comments = "%s[~%s] Said:{quote}%s{quote}\\\ \\\ " % (
                quoted_comments, comment['author'], comment['body'])

        if quoted_comments:
            quoted_comments = "Comments from the parent issue:\\\ %s" % quoted_comments
            self.jira.add_comment(new_issue, quoted_comments)

        return new_issue

    # method to transistion audit ticket
    def get_group_members(self, group_name):
        """
        Returns the members of a 
        """
        group = self.jira.groups(query=group_name)['groups'][0]['name']
        members = self.jira.group_members(group)
        return members

    def auto_assign(self):
        """
        Looks up new INDEXREP issues with an empty assignee and non-agent
        reporter and assigns them to the user in the content-acquisition user 
        group with the fewest assigned contect-acquistion tickets. 

        """
        # filter 20702 returns issues that need to auto assigned
        jql_query = self.jira.filter("20702").jql
        issues = self.jira.search_issues(jql_query)

        #filter 21200 returns non-resolved assigned issues
        assigned_issues_query = self.jira.filter("21200").jql

        # cycle through each issue and assign it to the user in
        # content acquisition with the fewest assigned tickets
        for issue in issues:
            username = self.user_with_fewest_issues('content-acquisition',
                                                    assigned_issues_query)

            reporter = issue.fields.reporter.key
            watch_list = self.toggle_watchers("remove", issue)
            self.jira.assign_issue(issue=issue, assignee=username)
            message = ("[~%s], this issue has been automically assigned "
                       "to [~%s].") % (reporter, username)
            self.jira.add_comment(issue.key, message)
            self.toggle_watchers("add", issue, watch_list)

    def remind_reporter_to_close(self):
        """
        Comments on all non-closed resolved issues that are 13 days without a
        change. Notifies the reporter it will be closed in 24 hours and adds a
        label to the issue that is used as a lookup key by the close method.

        """
        issues = self.jira.search_issues('resolution != EMPTY AND \
            status not in (closed, "Quality Control", Reopened, Merged, open) \
            AND updated <= -13d and project not in (INDEXREP)')
        for issue in issues:
            reporter = issue.fields.reporter.key
            message = (
                "[~%s], this issue has been resolved for 13 days. It will be "
                "closed automatically in 24 hours.") % reporter
            watch_list = self.toggle_watchers("remove", issue)
            self.jira.add_comment(issue.key, message)
            issue.fields.labels.append(self.ac_label)
            issue.update(fields={"labels": issue.fields.labels})
            self.toggle_watchers("add", issue, watch_list)

    def close_resolved(self):
        """
        Looks up all issues labeled for auto-closing that have not been updated
        in 24 hours and closes them. Ignores INDEXREP so as to not interfere
        with the auditing process.

        """
        issues = self.jira.search_issues('resolution != EMPTY AND \
            status not in (closed, "Quality Control", Reopened, Merged, open, \
            passed,staged) AND project not in (INDEXREP) \
            AND updated <= -24h \
            AND labels in (auto-close-24-hours)')
        for issue in issues:
            reporter = issue.fields.reporter.key
            message = (
                "[~%s], this issue has closed automatically.") % reporter
            close_me = self.close_issue(issue)
            self.jira.add_comment(issue.key, message)

    def close_issue(self, issue):
        """
        Closes the issue passed to it with a resolution of fixed.
        Inputs: Issue: the issue object to close
        Returns: True|False
        
        """
        trans = self.jira.transitions(issue)
        success_flag = False
        for tran in trans:
            tran_name = tran['name'].lower()
            if 'close' in tran_name or 'complete' in tran_name:
                try:
                    self.jira.transition_issue(issue, tran['id'],
                                               {'resolution': {
                                                   'id': '1'
                                               }})
                #some close transitions don't have a resolution screen
                except:  #open ended, but the JIRAError exception is broken.
                    self.jira.transition_issue(issue, tran['id'])
                success_flag = True
        return success_flag

    def clear_auto_close_label(self):
        """
        Clears the auto-close label from issues that have been re-opened
        since the auto-close reminder was posted.

        """
        issues = self.jira.search_issues(
            'status in ("Quality Control", Reopened, Merged, open) \
            AND labels in (auto-close-24-hours)')
        for issue in issues:
            label_list = issue.fields.labels
            watch_list = self.toggle_watchers("remove", issue)
            label_list.remove(self.ac_label)
            issue.update(fields={"labels": label_list})
            self.toggle_watchers("add", issue, watch_list)

    def toggle_watchers(self, action, issue, watch_list=[]):
        """
        Internal method that either adds or removes the watchers of an issue. If
        it removes them,it returns a list of users that were removed. If it 
        adds, it returns an updated list of watchers.
        
        Inputs:
        :action: String "add"|"remove". The action to take
        :issue:  Issue whose watchers list is being modified
        :watch_list: list of users. Optional for remove. Required for add.
        
        Returns:
        :issue_watcher: List of users who are or were watching the issue.
        
        """
        if action == "remove":
            issue_watchers = self.jira.watchers(issue).watchers
            for issue_watcher in issue_watchers:
                self.jira.remove_watcher(issue, issue_watcher.name)
        else:
            for old_watcher in watch_list:
                self.jira.add_watcher(issue, old_watcher.name)
            issue_watchers = self.jira.watchers(issue).watchers
        return issue_watchers

    def label_contains(self, issue, search_string):
        """
        Internal method that searches the labels of an issue for a given string
        value. It allows filtering that is roughly "labels ~ 'string'", which
        is not supported by JQL.

        Inputs:
        :issue: Jira issue object that is being checked
        :search_string: the string value being checked for

        Returns:
        True|False  True if search_string exists in any label.

        """
        return any(search_string in label for label in issue.fields.labels)

    def user_with_fewest_issues(self, group, query):
        """
        Given a query, return the username of the use with the fewest assigned
        issues in the result set.
        
        Inputs:
        group: the group of users for which to count issues.
        query: the issues to lookup. Should be a JQL string.
        
        """
        members = self.get_group_members(group)

        issues = self.jira.search_issues(query)

        member_count = {}

        for member in members:
            member_count[member] = 0

        # perform the count anew for each ticket
        for issue in issues:
            if issue.fields.assignee:
                assignee = issue.fields.assignee.key
            else:
                assignee = None
            if assignee in members and not self.label_contains(issue, "wait"):
                member_count[assignee] = member_count[assignee] + 1

        #sort the list so that the user with the lowest count is first
        member_count_sorted = sorted(member_count.items(),
                                     key=operator.itemgetter(1))
        # return the username of the user
        return str(member_count_sorted[0][0])
Esempio n. 18
0
class Jira(object):
    '''
    Encapsulates interaction with Jira server.
    '''

    def __init__(self, config):
        self.config = config
        self.options = {'server': config['server']}
        self.server = JIRA(self.options,
                basic_auth=(config['username'], config['password']))

    def query(self, jql_query):
        '''
        Run a JQL query.
        '''
        return self.server.search_issues(jql_query)

    def create_issue(self, summary, description, kind='Bug'):
        '''
        Create a Jira issue -- defaults to a bug.
        '''

        new_issue = self.server.create_issue(
            project={'key': self.config['project']},
            summary=summary,
            description=summary,
#       components=[{'id': '10301', 'name': 'Server Engineering'}],
            assignee={'name': self.config['username']},
            issuetype={'name': kind})

        return new_issue

    def close_issue(self, ticket):
        issue = self.get_issue(ticket)
        transitions = self.server.transitions(issue)
        transition_names = [x['name'] for x in transitions]
        if 'Stop Progress' in transition_names:
            self.transition_issue(ticket, status='Stop Progress')
            transitions = self.server.transitions(issue)
            transition_names = [x['name'] for x in transitions]

        if 'Resolve Issue' in transition_names:
            self.transition_issue(ticket, status='Resolve Issue')
            return self.get_issue(ticket)
        else:
            raise InvalidJiraStatusException(
                    '\nattempt to close ticket failed. \
            \nValid transitions: {0}'.format(', '.join(transition_names)))

    def transition_issue(self, ticket, status='Resolve Issue'):

        issue = self.get_issue(ticket)
        transitions = self.server.transitions(issue)
        transition_names = [x['name'] for x in transitions]
        if status not in transition_names:
            raise InvalidJiraStatusException(
            '\n\'{0}\' is not a valid status for this Jira issue. \
            \nValid transitions: {1}'.format(
                status, ', '.join(transition_names)))
        _id = [x['id'] for x in transitions if x['name'] == status][0]
        # transition it:
        self.server.transition_issue(issue, _id)
        return self.get_issue(ticket)

#users = self.jira.server.search_assignable_users_for_issues('',
#        project='CIGNAINC', maxResults=500)

    def add_watcher(self, ticket, person):

        issue = self.get_issue(ticket)
        self.server.add_watcher(issue, person)
        return issue

    def list_reviewable(self):
        '''
        if a JQL query is defined in the config use it.  If not:
            if a named filter is defined in the config use it.  If not:
                use the predefined filter.
        '''

        section = self.config.get('review', None)
        jql = 'status IN ("Ready for Review", "In Review") \
            ORDER BY priority, updatedDate ASC'

        if not section:
            return self.query(jql)

        jql_config = section.get('jql', None)
        if jql_config:
            return self.query(jql_config)

        filter_name = section.get('filter', None)

        if filter_name:
            filters = self.server.favourite_filters()
            jql = [x.jql for x in filters if x.name == filter_name][0]
            return self.query(jql)

        return self.query(jql)

    def list_issues(self):
        '''
        if a JQL query is defined in the config use it.  If not:
            if a named filter is defined in the config use it.  If not:
                use the predefined filter.
        '''

        section = self.config.get('list', None)
        jql = 'assignee=currentUser() \
                AND status != Closed AND status != Resolved'

        if not section:
            return self.query(jql)

        jql_config = section.get('jql', None)
        if jql_config:
            return self.query(jql_config)

        filter_name = section.get('filter', None)

        if filter_name:
            filters = self.server.favourite_filters()
            jql = [x.jql for x in filters if x.name == filter_name][0]
            return self.query(jql)

        return self.query(jql)

    def delete_issue(self, ticket):

        issue = self.get_issue(ticket)
        issue.delete()

    def assign_issue(self, ticket, assignee):
        '''
        Given an issue and an assignee, assigns the issue to the assignee.
        '''
        issue = self.get_issue(ticket)
        self.server.assign_issue(issue, assignee)
        return issue

    def add_comment(self, ticket, comment):
        '''
        Given an issue and a comment, adds the comment to the issue.
        '''
        issue = self.get_issue(ticket)
        self.server.add_comment(issue, comment)
        return issue

    def get_issue(self, ticket):
        '''
        Given an issue name, returns a Jira instance of that issue.
        '''
        if isinstance(ticket, Issue):
            ticket = ticket.key

        return self.server.issue('{0}'.format(ticket))
Esempio n. 19
0
print "Creating ticket in project {0} with summary, description, issue type, priority, and reporter from {1}...".format(proj, t1.key),
t2 = sup.create_issue(t2_fields)
print "success (ticket {0} created)".format(t2.key)

print "Adding watchers from {0} to {1}...".format(t1.key, t2.key),
for w in sup.watchers(t1).watchers:
	sup.add_watcher(t2, w.name)
print "success"

print "Linking {0} to {1} with link type of {2}...".format(t1.key, t2.key, link_type),
sup.create_issue_link(link_type, t1.key, t2.key)
print "success"

print "Adding closing comment to {0}...".format(t1.key),
t1_comment = 'Closing this ticket, this issue can be further communicated in linked roadmap ticket {0}.\n\nThanks'.format(t2.key)
sup.add_comment(t1.key, t1_comment)
print "success"

print "Silently closing {0}...".format(t1.key),
sup.transition_issue(t1, getTransitionID(t1, 'Silently Close'), resolution={'id': getResolutionID('Transitioned to Roadmap')})
print "success"

print "Adding comment to {0} directing to refer to linked service desk ticket {1} for previous communication...".format(t2.key, t1.key),
t2_comment = 'Please refer to linked Service Desk ticket {0} for previous communication on this issue.'.format(t1.key)
sup.add_comment(t2, t2_comment)
print "success"


# below prints transition types available
#trans = sup.transitions(t1)
#[(t['id'], t['name']) for t in trans]
Esempio n. 20
0
                        logger.debug('RT comment already exists, skipping')
                        syslog.syslog(syslog.LOG_DEBUG, 'RT comment already exists, skipping')
                        break
    
                if not comment_exists:
                    comment_body = 'Date: ' + rt_format_comment_time(comment_date) + '\nFrom: ' + c['Creator'] + '\nTicket ID: ' + ticket_id + '\nSubject: ' + scrubbed_title + '\nAction: ' + c['Description'] + '\n\n' + c['Content']
                    # JIRA can't store comments more than 32,000 chars in length
                    truncated_comment = (comment_body[:31997] + '...') if len(comment_body) > 32000 else comment_body

                    # Only create the JIRA comment if this isn't an internal action to rt2jira
                    internal_action = 'Comments added by ' + config.get('rt', 'username')
                    new_comment = None
                    if internal_action not in c['Description']:
                        logger.info('Adding new comment to (' + jira_issue.key + ') from (' + comment_creator + ') on (' + rt_format_comment_time(comment_date) + ')')
                        syslog.syslog(syslog.LOG_INFO, 'Adding new comment to (' + jira_issue.key + ') from (' + comment_creator + ') on (' + rt_format_comment_time(comment_date) + ')')
                        new_comment = jira.add_comment(jira_issue, truncated_comment)
    
                    user = find_user(comment_creator, config.getint('jira', 'find_user_algo_type_comment'), config.get('jira', 'find_user_projects'))
                    if user:
                        # Auto-add ticket commenter as watcher to the JIRA ticket.
                        logger.debug('Adding (' + user.name + ') as a watcher to (' + jira_issue.key + ')')
                        syslog.syslog(syslog.LOG_DEBUG, 'Adding (' + user.name + ') as a watcher to (' + jira_issue.key + ')')
                        jira.add_watcher(jira_issue, user.name)
                    else:
                        logger.debug('Unable to find equivalent RT commenter in JIRA: ' + comment_creator)
                        syslog.syslog(syslog.LOG_DEBUG, 'Unable to find equivalent RT commenter in JIRA: ' + comment_creator)
    
                    # Assign ticket, if RT ticket was taken.
                    if 'Taken by' in c['Description']:
                        ticket_owner = re.sub('Taken by ', '', c['Description'])
                        user = find_user(ticket_owner, config.getint('jira', 'find_user_algo_type_comment'), config.get('jira', 'find_user_projects'))
Esempio n. 21
0
class JiraAlerter(Alerter):
    """ Creates a Jira ticket for each alert """
    required_options = frozenset(['jira_server', 'jira_account_file', 'jira_project', 'jira_issuetype'])

    def __init__(self, rule):
        super(JiraAlerter, self).__init__(rule)
        self.server = self.rule['jira_server']
        self.get_account(self.rule['jira_account_file'])
        self.project = self.rule['jira_project']
        self.issue_type = self.rule['jira_issuetype']
        self.component = self.rule.get('jira_component')
        self.label = self.rule.get('jira_label')
        self.description = self.rule.get('jira_description', '')
        self.assignee = self.rule.get('jira_assignee')
        self.max_age = self.rule.get('jira_max_age', 30)
        self.priority = self.rule.get('jira_priority')
        self.bump_tickets = self.rule.get('jira_bump_tickets', False)
        self.bump_not_in_statuses = self.rule.get('jira_bump_not_in_statuses')
        self.bump_in_statuses = self.rule.get('jira_bump_in_statuses')

        if self.bump_in_statuses and self.bump_not_in_statuses:
            msg = 'Both jira_bump_in_statuses (%s) and jira_bump_not_in_statuses (%s) are set.' % \
                  (','.join(self.bump_in_statuses), ','.join(self.bump_not_in_statuses))
            intersection = list(set(self.bump_in_statuses) & set(self.bump_in_statuses))
            if intersection:
                msg = '%s Both have common statuses of (%s). As such, no tickets will ever be found.' % (msg, ','.join(intersection))
            msg += ' This should be simplified to use only one or the other.'
            logging.warning(msg)

        self.jira_args = {'project': {'key': self.project},
                          'issuetype': {'name': self.issue_type}}

        if self.component:
            self.jira_args['components'] = [{'name': self.component}]
        if self.label:
            self.jira_args['labels'] = [self.label]
        if self.assignee:
            self.jira_args['assignee'] = {'name': self.assignee}

        try:
            self.client = JIRA(self.server, basic_auth=(self.user, self.password))
            self.get_priorities()
        except JIRAError as e:
            # JIRAError may contain HTML, pass along only first 1024 chars
            raise EAException("Error connecting to JIRA: %s" % (str(e)[:1024]))

        try:
            if self.priority is not None:
                self.jira_args['priority'] = {'id': self.priority_ids[self.priority]}
        except KeyError:
            logging.error("Priority %s not found. Valid priorities are %s" % (self.priority, self.priority_ids.keys()))

    def get_priorities(self):
        """ Creates a mapping of priority index to id. """
        priorities = self.client.priorities()
        self.priority_ids = {}
        for x in range(len(priorities)):
            self.priority_ids[x] = priorities[x].id

    def set_assignee(self, assignee):
        self.assignee = assignee
        if assignee:
            self.jira_args['assignee'] = {'name': assignee}
        elif 'assignee' in self.jira_args:
            self.jira_args.pop('assignee')

    def find_existing_ticket(self, matches):
        # Default title, get stripped search version
        if 'alert_subject' not in self.rule:
            title = self.create_default_title(matches, True)
        else:
            title = self.create_title(matches)

        # This is necessary for search for work. Other special characters and dashes
        # directly adjacent to words appear to be ok
        title = title.replace(' - ', ' ')

        date = (datetime.datetime.now() - datetime.timedelta(days=self.max_age)).strftime('%Y-%m-%d')
        jql = 'project=%s AND summary~"%s" and created >= "%s"' % (self.project, title, date)
        if self.bump_in_statuses:
            jql = '%s and status in (%s)' % (jql, ','.join(self.bump_in_statuses))
        if self.bump_not_in_statuses:
            jql = '%s and status not in (%s)' % (jql, ','.join(self.bump_not_in_statuses))
        try:
            issues = self.client.search_issues(jql)
        except JIRAError as e:
            logging.exception("Error while searching for JIRA ticket using jql '%s': %s" % (jql, e))
            return None

        if len(issues):
            return issues[0]

    def comment_on_ticket(self, ticket, match):
        text = unicode(JiraFormattedMatchString(self.rule, match))
        timestamp = pretty_ts(match[self.rule['timestamp_field']])
        comment = "This alert was triggered again at %s\n%s" % (timestamp, text)
        self.client.add_comment(ticket, comment)

    def alert(self, matches):
        title = self.create_title(matches)

        if self.bump_tickets:
            ticket = self.find_existing_ticket(matches)
            if ticket:
                elastalert_logger.info('Commenting on existing ticket %s' % (ticket.key))
                for match in matches:
                    try:
                        self.comment_on_ticket(ticket, match)
                    except JIRAError as e:
                        logging.exception("Error while commenting on ticket %s: %s" % (ticket, e))
                if self.pipeline is not None:
                    self.pipeline['jira_ticket'] = ticket
                    self.pipeline['jira_server'] = self.server
                return

        description = self.description + '\n'
        for match in matches:
            description += unicode(JiraFormattedMatchString(self.rule, match))
            if len(matches) > 1:
                description += '\n----------------------------------------\n'

        self.jira_args['summary'] = title
        self.jira_args['description'] = description

        try:
            self.issue = self.client.create_issue(**self.jira_args)
        except JIRAError as e:
            raise EAException("Error creating JIRA ticket: %s" % (e))
        elastalert_logger.info("Opened Jira ticket: %s" % (self.issue))

        if self.pipeline is not None:
            self.pipeline['jira_ticket'] = self.issue
            self.pipeline['jira_server'] = self.server

    def create_default_title(self, matches, for_search=False):
        # If there is a query_key, use that in the title
        if 'query_key' in self.rule and self.rule['query_key'] in matches[0]:
            title = 'ElastAlert: %s matched %s' % (matches[0][self.rule['query_key']], self.rule['name'])
        else:
            title = 'ElastAlert: %s' % (self.rule['name'])

        if for_search:
            return title

        title += ' - %s' % (pretty_ts(matches[0][self.rule['timestamp_field']], self.rule.get('use_local_time')))

        # Add count for spikes
        count = matches[0].get('spike_count')
        if count:
            title += ' - %s+ events' % (count)

        return title

    def get_info(self):
        return {'type': 'jira'}
Esempio n. 22
0
class Housekeeping():
    """
    This class is the container for all automated Jira functions performed
    by the Housekeeping agent.
    
    """ 
    def __init__(self):
        # class variables
        self.ac_label =  u'auto-close-24-hours'
        self.audit_delay = '-72h'
        self.audit_projects = "INDEXREP" #comma delimited project keys
        # open JIRA API Connection
        self.jira = JIRA(options=secrets.options, 
                            basic_auth=secrets.housekeeping_auth) 
    
        # commands to run
        self.content_acquisition_auto_qc()
        self.auto_assign()
        self.remind_reporter_to_close()
        self.close_resolved() 
        self.clear_auto_close_label()
        self.resolved_issue_audit()
        self.handle_audited_tickets()

    def content_acquisition_auto_qc(self):
        """
        Takes INDEXREP issues that have been Merged for 30+ minutes and 
        transitions them to Quality Control. It then adds a comment that
        tags the reporter to inform them that the issue is ready for review.

        """
        issues = self.jira.search_issues(
            'project=INDEXREP and status=Merged and updated<="-30m"')
        
        for issue in issues:
            reporter = issue.fields.reporter.key
            message = '[~%s], this issue is ready for QC.' % reporter
            """ 
            771 is the transition ID spedific to this step for this project.
            Anything more generic will need to parse the transitions list.
            """
            self.jira.transition_issue(issue.key,'771')
            self.jira.add_comment(issue.key, message)
    
    def handle_audited_tickets(self):
        """
        Handles audit tickets that are failed. Closed tickets are ignored. Failed 
        tickets trigger the creation of a new ticket in the same project as the 
        original ticket.
        
        Inputs: None
        Returns: None
        
        """
        issues = self.jira.search_issues(   # get all the ADT issues
            'project=ADT and status="Failed Audit"')
        
        # For each failed issue, generate a new work ticket then close this one
        for issue in issues:
            #BUID
            adt_buid=issue.fields.customfield_10502
            #WCID
            adt_wcid=issue.fields.customfield_10501
            #Indexing Type
            adt_indexing_type=issue.fields.customfield_10500
            #comments
            adt_comments = []
            for comment in self.jira.comments(issue):
                node = {
                    'body':self.jira.comment(issue,comment).body,
                    'author': self.jira.comment(issue,comment).author.key
                }
                adt_comments.append(node)
                        
            link_list = [issue.key,] # first linked ticket should be this audit ticket
            for link in issue.fields.issuelinks: # grab the rest of links
                try:
                    link_list.append(link.outwardIssue.key)
                except AttributeError:
                    pass
            
            # capture orignal tick and project
            original_ticket = issue.fields.summary.split("[")[1].split("]")[0]
            original_project = original_ticket.split("-")[0]
            
            # build the new summary by parsing the audit summary
            indexrep_summary = issue.fields.summary #build the summary
            indexrep_summary = indexrep_summary.replace("compliance audit - ","")
            indexrep_summary = indexrep_summary.split("[")[0]
            indexrep_summary = ' %s - Failed Audit' % (indexrep_summary)
            
            # Build the issue description
            message = 'This issue failed audit. Please review %s and make any \
                necessary corrections.' % original_ticket

            # Construct the watcher list and de-dupe it
            watcher_list = [issue.fields.assignee.key,]
            for w in self.jira.watchers(issue).watchers:
                watcher_list.append(w.key)
            watcher_list = set(watcher_list)
            
            # get the reporter (reporter is preserved from audit to issue)
            reporter = issue.fields.reporter.key
            
            # Generate the new issue, then close the audit ticket.    
            new_issue = self.make_new_issue(original_project,"EMPTY",reporter,
                                                                    indexrep_summary,message,
                                                                    watcher_list,link_list,adt_buid,
                                                                    adt_wcid,adt_indexing_type,adt_comments)            
            close_me = self.close_issue(issue.key)
                        
    
    def resolved_issue_audit(self,delay="",projects=""):
        """
        TAKES issues that have been resolved from specified projectsfor a set 
        interval and creates a new ticket in AUDIT, closes the INDEXREP ticket, 
        and then assigns it to the audit user specified in the self.qa_auditor role.
        
        Inputs:
        :delay:      how long an issue should be resoved before being picked up
                        by this script. Defaults to class level variable
        :projects:  which projects are subject to auditing. Defaults to class level
                        variable
        Returns:    Error message or Nothing
        
        """
        delay = self.audit_delay if not delay else delay
        projects = self.audit_projects if not projects else projects
        # get all the issues from projects in the audit list
        issue_query = 'project in (%s) and status=Resolved and resolutiondate \
            <="%s"' % (projects,delay)
        issues = self.jira.search_issues(issue_query) 
        
        # get the users who can be assigned audit tickets. This should be just one person
        qa_members = self.get_group_members("issue audits")
        if len(qa_members)==1:
            qa_auditor=qa_members.keys()[0]
        else:
            # for now, throw an error. Later, assign to user with fewer ADT tickets
            # this will also mean turning the code in auto_assign into a method (DRY)
            return "Error: There is more than one possible auditor"
        
        # cycle through them and create a new ADT ticket for each 
        for issue in issues:
            #BUID
            ind_buid=issue.fields.customfield_10502
            #WCID
            ind_wcid=issue.fields.customfield_10501
            #Indexing Type
            ind_indexing_type=issue.fields.customfield_10500
            link_list = [issue.key,]
            for link in issue.fields.issuelinks: # grab the rest of links
                try:
                    link_list.append(link.outwardIssue.key)
                except AttributeError:
                    pass
            # build the new ticket summary based on the issue being audited
            # [ISSUE=123] is used to preserve the original issue key. Replace any brackets with () 
            # to prevent read errors later.
            adt_summary = issue.fields.summary.replace("[","(").replace("]",")")
            adt_summary = 'compliance audit - %s [%s]' % (adt_summary,issue.key)
            # build the description
            message = '[~%s], issue %s is ready to audit.' % (qa_auditor, issue.key)
            
            #build the watcher list, including original reporter and assignee of the audited ticket
            watcher_list = []
            for w in self.jira.watchers(issue).watchers:
                watcher_list.append(w.key)
            reporter = issue.fields.reporter.key
            try:
                original_assignee = issue.fields.assignee.key
            except AttributeError:
                original_assignee="EMPTY"         
           
            # make the audit ticket
            new_issue = self.make_new_issue("ADT",qa_auditor,reporter,
                adt_summary,message,watcher_list,link_list,ind_buid,
                ind_wcid,ind_indexing_type)
           
            # close the INDEXREP ticket
            close_me = self.close_issue(issue.key)
            
            # add comment to indexrep ticket
            link_back_comment = "This issue has been closed. The audit ticket is %s" % new_issue
            self.jira.add_comment(issue.key, link_back_comment)
            
        
    def make_new_issue(self,project,issue_assignee,issue_reporter,summary,
                                      description="",watchers=[],links=[],buid="",wcid="",
                                      indexing_type="",comments=[],issuetype="Task"):
        """
        Creates a new issue with the given parameters.
        Inputs:
        *REQUIRED*
            :project:   the jira project key in which to create the issue
            :issue_assignee:    user name who the issue will be assigned to
            :issue_reporter:    user name of the issue report
            :summary:   string value of the issue summary field
            *OPTIONAL*
            :description: Issue description. Defaults to empty string
            :watchers: list of user names to add as issue watchers
            :link:  list of issue keys to link to the issue as "Related To"
            :issuetype: the type of issue to create. Defaults to type.
            :buid: business unit - custom field 10502
            :wcid: wrapping company id - custom field 10501
            :indexing_type: the indexing type - custom field 10500
            :comments: list dictionaries of comments and authors to auto add.
        Returns: Jira Issue Object
        
        """
        issue_dict = {
            'project':{'key':project},
            'summary': summary,
            'issuetype': {'name':issuetype},
            'description':description,        
            }
        new_issue = self.jira.create_issue(fields=issue_dict)
        
        # assign the audit tick to auditor
        new_issue.update(assignee={'name':issue_assignee})
        new_issue.update(reporter={'name':issue_reporter})
        
        # add watchers to audit ticket (reporter, assignee, wacthers from indexrep ticket)
        for watcher in watchers:
            self.jira.add_watcher(new_issue,watcher)
        
        # link the audit ticket back to indexrep ticket
        for link in links:
            self.jira.create_issue_link('Relates',new_issue,link)
        
        # add custom field values if set
        if buid:
            new_issue.update(fields={'customfield_10502':buid})
        if wcid:
            new_issue.update(fields={'customfield_10501':wcid})
        if indexing_type:
            new_issue.update(fields={'customfield_10500':{'value':indexing_type.value}})
        
        # add comments
        quoted_comments = ""
        for comment in comments:
            quoted_comments = "%s[~%s] Said:{quote}%s{quote}\\\ \\\ " % (quoted_comments,comment['author'],comment['body'])
            
        if quoted_comments:
            quoted_comments = "Comments from the parent issue:\\\ %s" % quoted_comments
            self.jira.add_comment(new_issue,quoted_comments)
            
        return new_issue
            
    # method to transistion audit ticket    
    def get_group_members(self, group_name):
        """
        Returns the members of a 
        """
        group = self.jira.groups(
            query=group_name
            )['groups'][0]['name']
        members = self.jira.group_members(group)
        return members
    
    def auto_assign(self):
        """
        Looks up new INDEXREP issues with an empty assignee and non-agent
        reporter and assigns them to the user in the content-acquisition user 
        group with the fewest assigned contect-acquistion tickets. 

        """        
        # filter 20702 returns issues that need to auto assigned
        jql_query = self.jira.filter("20702").jql        
        issues = self.jira.search_issues(jql_query)
        
        #filter 21200 returns non-resolved assigned issues
        assigned_issues_query = self.jira.filter("21200").jql
        
        # cycle through each issue and assign it to the user in 
        # content acquisition with the fewest assigned tickets
        for issue in issues:
            username = self.user_with_fewest_issues('content-acquisition', 
                                                    assigned_issues_query)
            
            reporter = issue.fields.reporter.key
            watch_list = self.toggle_watchers("remove",issue)
            self.jira.assign_issue(issue=issue,assignee=username)
            message = ("[~%s], this issue has been automically assigned "
                "to [~%s].") % (reporter,username)
            self.jira.add_comment(issue.key, message)
            self.toggle_watchers("add",issue,watch_list)            
            
        
    def remind_reporter_to_close(self):
        """
        Comments on all non-closed resolved issues that are 13 days without a
        change. Notifies the reporter it will be closed in 24 hours and adds a
        label to the issue that is used as a lookup key by the close method.

        """
        issues = self.jira.search_issues(
            'resolution != EMPTY AND \
            status not in (closed, "Quality Control", Reopened, Merged, open) \
            AND updated <= -13d and project not in (INDEXREP)')
        for issue in issues:
            reporter = issue.fields.reporter.key
            message = (
                "[~%s], this issue has been resolved for 13 days. It will be "
                "closed automatically in 24 hours.") % reporter
            watch_list = self.toggle_watchers("remove",issue)
            self.jira.add_comment(issue.key,message)
            issue.fields.labels.append(self.ac_label)
            issue.update(fields={"labels": issue.fields.labels})
            self.toggle_watchers("add",issue, watch_list)

    def close_resolved(self):
        """
        Looks up all issues labeled for auto-closing that have not been updated
        in 24 hours and closes them. Ignores INDEXREP so as to not interfere
        with the auditing process.

        """
        issues = self.jira.search_issues(
            'resolution != EMPTY AND \
            status not in (closed, "Quality Control", Reopened, Merged, open, \
            passed,staged) AND project not in (INDEXREP) \
            AND updated <= -24h \
            AND labels in (auto-close-24-hours)')
        for issue in issues:
            reporter = issue.fields.reporter.key
            message = (
                "[~%s], this issue has closed automatically.") % reporter
            close_me = self.close_issue(issue)
            self.jira.add_comment(issue.key,message)
            
    def close_issue(self, issue):
        """
        Closes the issue passed to it with a resolution of fixed.
        Inputs: Issue: the issue object to close
        Returns: True|False
        
        """
        trans = self.jira.transitions(issue)
        success_flag = False
        for tran in trans:
            tran_name = tran['name'].lower()
            if 'close' in tran_name or 'complete' in tran_name:
                try:
                    self.jira.transition_issue(issue,tran['id'],{'resolution':{'id':'1'}})
                #some close transitions don't have a resolution screen
                except: #open ended, but the JIRAError exception is broken.
                    self.jira.transition_issue(issue,tran['id'])
                success_flag = True
        return success_flag
                
    def clear_auto_close_label(self):
        """
        Clears the auto-close label from issues that have been re-opened
        since the auto-close reminder was posted.

        """
        issues = self.jira.search_issues(
            'status in ("Quality Control", Reopened, Merged, open) \
            AND labels in (auto-close-24-hours)')
        for issue in issues:
            label_list =  issue.fields.labels
            watch_list = self.toggle_watchers("remove",issue)
            label_list.remove(self.ac_label)
            issue.update(fields={"labels": label_list})
            self.toggle_watchers("add",issue, watch_list)


    def toggle_watchers(self,action,issue,watch_list=[]):
        """
        Internal method that either adds or removes the watchers of an issue. If
        it removes them,it returns a list of users that were removed. If it 
        adds, it returns an updated list of watchers.
        
        Inputs:
        :action: String "add"|"remove". The action to take
        :issue:  Issue whose watchers list is being modified
        :watch_list: list of users. Optional for remove. Required for add.
        
        Returns:
        :issue_watcher: List of users who are or were watching the issue.
        
        """
        if action=="remove":
            issue_watchers = self.jira.watchers(issue).watchers
            for issue_watcher in issue_watchers:
                self.jira.remove_watcher(issue,issue_watcher.name)
        else:
            for old_watcher in watch_list:
                self.jira.add_watcher(issue,old_watcher.name)
            issue_watchers = self.jira.watchers(issue).watchers
        return issue_watchers

    def label_contains(self,issue,search_string):        
        """
        Internal method that searches the labels of an issue for a given string
        value. It allows filtering that is roughly "labels ~ 'string'", which
        is not supported by JQL.

        Inputs:
        :issue: Jira issue object that is being checked
        :search_string: the string value being checked for

        Returns:
        True|False  True if search_string exists in any label.

        """
        return any(search_string in label for label in issue.fields.labels)    
    
    def user_with_fewest_issues(self,group,query):
        """
        Given a query, return the username of the use with the fewest assigned
        issues in the result set.
        
        Inputs:
        group: the group of users for which to count issues.
        query: the issues to lookup. Should be a JQL string.
        
        """
        members = self.get_group_members(group)
        
        issues = self.jira.search_issues(query)
        
        member_count = {}

        for member in members:
            member_count[member]=0

        # perform the count anew for each ticket
        for issue in issues:
            if issue.fields.assignee:
                assignee = issue.fields.assignee.key
            else:
                assignee = None
            if assignee in members and not self.label_contains(issue,"wait"):
                member_count[assignee] = member_count[assignee]+1
                
        #sort the list so that the user with the lowest count is first
        member_count_sorted = sorted(member_count.items(), 
            key=operator.itemgetter(1))
        # return the username of the user 
        return str(member_count_sorted[0][0]) 
Esempio n. 23
0
class MIEJIRA(object):
    """
    A bridge interface to connect CI and JIRA server
    
    This class provide basic actions to interact with JIRA server . 
    Includes three actions :
    1:Create issue
    2:Query issue
    3:Update issue
    4:Transition issue
    """

    # create a new MIEJIRA instance
    __t_jira = None

    # default jira server's address 
    __server = "http://jira.lab.tclclouds.com/"

    # default username and password 
    __basic_auth = ("CITest", "tcltest123456")

    # property extends from JIRA-Python library
    __options = None
    __oauth = None
    __validate = None
    __async = False
    __logging = False

    def __init__(self, server=None, basic_auth=None):
        """
        Construct a MIEJIRA client instance
        
        create a new JIRA instance to interact JIRA server
        
        Args:
            server: the server's address to interact
            basic_auth: a tuple pf username and password when establishing a session via HTTP BASIC authentication.
        """
        if server is not None:
            self.__server = server
        if basic_auth is not None:
            self.__basic_auth = basic_auth
        self.__t_jira = JIRA(self.__server, self.__options, self.__basic_auth, self.__oauth, self.__validate,
                             self.__async, self.__logging)

    def __get_component_id_by_name(self, component_name):
        components = self.__get_all_components("CI")
        if component_name in components.keys():
            component_id = components[component_name]
        else:
            raise Exception("Component name : %s is not exist , please check it" % component_name)
        return component_id

    def __get_all_components(self, projectname):
        formated_projects = {}
        project_ci = self.__t_jira.project(projectname)
        components = self.__t_jira.project_components(project_ci)
        for component in components:
            formated_projects[component.name] = component.id
        return formated_projects

    def create_issue(self, summary, description, component, reoccur_times, version, comment, priority="3"):

        """
        Create a new issue and return an issue Resource for it.

        :param summary: issue's summary information
        :param description: issue's detail information
        :param component: current issue's relative project . For example:AppCenter,GameCenter
        :param reoccur_times: issue re-occured times
        :param version: issue version
        :param comment: add comment
        :param priority: Id of priority. default value is 3
        :return: return a new dict that includes created issue_key,status_name,created_time,updated_time
        """
        print("********************Create New Issue****************************")
        # When create issue has succeed , return some scoped values to client
        return_value = {}

        # Maybe need to match it
        componentId = self.__get_component_id_by_name(component)
        print("component id : ", componentId)

        # Create issue dict property
        issue_dict = {
                "project": {
                    "key": "CI"
                },
                "summary": summary,
                "description": description,
                "versions": [{ "name": "None" }],
                "issuetype": {
                    "name": "CI Bug"
                },
                "priority": {
                    "id": priority
                },
                "customfield_10100": reoccur_times,
                "components": [
                    {
                        "id": componentId
                    }
                ],
                "customfield_10101": version
            }

        # First , let create a new issue
        created_issue = self.__t_jira.create_issue(fields=issue_dict)

        # Second , comment it
        # print("Begain comment")
        self.__t_jira.add_comment(created_issue.key, comment)
        # print("End comment")

        # combine the return values
        return_value["issue_key"] = created_issue.key
        return_value["status_name"] = created_issue.fields.status.name
        return_value["created_time"] = self.__format_time(created_issue.fields.created)
        return_value["updated_time"] = self.__format_time(created_issue.fields.updated)
        return_value["version_name"] = created_issue.fields.customfield_10101
        return return_value

    def query_issue_bykey(self, issue_keys):
        """
        Query issues's status and updated time
        
        Args:
            issue_keys: a tuple or list contains issue's keys to query
        
        Returns:
            return a new dict that contains each issue_keys's issue info 
            issue info include two fields: status_name , updated_time
        """
        print("************Query Issue*************")
        return_value = {}
        for issue_key in issue_keys:
            print("query issue_key : ", issue_key)
            issue = self.__t_jira.issue(issue_key)
            cur_info = (
            issue.fields.status.name, self.__format_time(issue.fields.updated), issue.fields.customfield_10101,
            issue.fields.customfield_10100)
            return_value[issue_key] = cur_info
        return return_value

    def query_issue(self, component, status):
        """
        According to status and component to query issues

        :param component: issue's component . Ex. APPCenter
        :param status: issue's status
        :return:
        return a dictionary of issue that fit the condition
        """
        print("**************Query Issue***************")
        return_value = []

        jql = "project = CI "

        if component:
            # Maybe need to match it
            componentId = self.__get_component_id_by_name(component)
            jql = jql + "AND component = " + componentId + " "

        if status:
            jql = jql + "AND status = " + status + " "

        issues = self.__t_jira.search_issues(jql, 0, 10000)

        for issue in issues:
            return_value.append(issue.key)

        return return_value

    def update_issue(self, issue_key, description, version, priority, reoccur_times, comment):
        """
        Update issue's fields

        :param issue_key: Key of the issue to perform the update action
        :param version: issue version
        :param priority: The id of priority action
        :param reoccur_times: occur times
        :param comment: issue's comment
        :return: if update action has succeed ,return True ; if not return False
        """
        print("***************************Update Issue******************")
        issue_dict = {}
        return_issue = None

        if description:
            issue_dict["description"] = description

        if version:
            issue_dict["customfield_10101"] = version

        if priority:
            issue_dict["priority"] = {
                "id": priority
            }
        if reoccur_times:
            issue_dict["customfield_10100"] = reoccur_times
        update_issue = self.__t_jira.issue(issue_key)
        update_issue.update(fields=issue_dict)
        # ADD COMMENT
        self.__t_jira.add_comment(update_issue.key, comment)

        queryed_issue = self.query_issue_bykey((issue_key,))
        return_issue = ((queryed_issue[issue_key])[0], (queryed_issue[issue_key])[1])
        return return_issue

    def transition_issue(self, issue_key, transition_name, comment_msg=None):
        """
        Transition issue's status, perform a transition on an issue.

        Args:
            issue_key: Key of the issue to perform the transition on
            transition_name: Name of the transition to perform
            comment: *Optional* String to add as comment to the issue when performing the transition.
        Returns:
            if update action has performed,return True. If not ,return False.
        """
        transition_id = None
        print("************Transition Issue*************")
        if transition_name == "CLOSE":
            # For real JIRA server CLOSE transition_id is 71
            # transition_id = "71"
            transition_id = ("71", "121")
            # transition_id = "2"
        elif transition_name == "REOPEN":
            # For real JIRA server REOPEN transition_id is 51
            # transition_id = "141"
            transition_id = ("141", "51")
            # transition_id = "3"
        else:
            raise Exception("transition name MUST BE CLOSE or REOPEN")
        update_flag = False
        issue = self.__t_jira.issue(issue_key)
        transitions = self.__t_jira.transitions(issue)
        for t in transitions:
            if t["id"] in transition_id:
                self.__t_jira.transition_issue(issue, t["id"], comment=comment_msg)
                update_flag = True
                break
        return update_flag

    def __format_time(self, time_str):
        """
        Format time
        
        "2015-04-28T13:19:53.000+0800" -> "2015-04-28 13:19:53"
        """
        formated_time = time_str[0:19].replace("T", " ")
        return formated_time

# if __name__ == '__main__':
#     jira = MIEJIRA()
#     for i in jira.query_issue('SHOP', 'Solved'):
#         print i

    # def query_issue_by_project(self):
    #         """
    #         According to status and component to query issues
    #
    #         :param component: issue's component . Ex. APPCenter
    #         :param status: issue's status
    #         :return:
    #         return a dictionary of issue that fit the condition
    #         """
    #         print("**************Query Issue***************")
    #         return_value = []
    #
    #         jql = "project = CI "
    #
    #         issues = self.__t_jira.search_issues(jql, 0, 10000)
    #
    #         for issue in issues:
    #             return_value.append(issue.key)
    #             # print issue
    #         return return_value
    #
    # def query_issue_by_key(self, issue_keys):
    #     """
    #     Query issues's status and updated time
    #
    #     Args:
    #         issue_keys: a tuple or list contains issue's keys to query
    #
    #     Returns:
    #         return a new dict that contains each issue_keys's issue info
    #         issue info include two fields: status_name , updated_time
    #     """
    #     print("************Query Issue*************")
    #     return_value = {}
    #     for issue_key in issue_keys:
    #         print("query issue_key : ", issue_key)
    #         issue = self.__t_jira.issue(issue_key)
    #         cur_info = (issue.fields.summary)
    #         # return_value[issue_key] = cur_info
    #         return_value[cur_info] = issue_key
    #     return return_value
Esempio n. 24
0
 def on_data(self, data):
     full_tweet = json.loads(data)
     db_tweets_result = db.tweets.insert_one(full_tweet)
     parsed_tweet = {}
     # saving id of tweet
     parsed_tweet['id'] = full_tweet['id']
     # saving user of the tweet
     parsed_tweet['user'] = full_tweet['user']['screen_name']
     # saving text of tweet
     parsed_tweet['text'] = self.clean_tweet(full_tweet['text'])
     # saving sentiment of tweet
     sentiment_score = self.get_tweet_sentiment(full_tweet['text'])
     if sentiment_score > 0.5:
         parsed_tweet['sentiment'] = 'positive'
     elif sentiment_score == 0.5:
         parsed_tweet['sentiment'] = 'neutral'
     else:
         parsed_tweet['sentiment'] = 'negative'
     parsed_tweet['score'] = sentiment_score
     db_sentiment_result = db.sentiment.insert_one(parsed_tweet)
     if sentiment_score < 0.5:
         jira_options = {'server': 'https://sidjira.atlassian.net/'}
         jira = JIRA(options=jira_options,
                     basic_auth=('*****@*****.**', 'ssap4321'))
         searchString = 'project="MSD" and status!=done and summary~"' + full_tweet[
             'user']['screen_name'] + '"'
         try:
             tickets = jira.search_issues(searchString)
         except:
             print('Error in searching Jira')
         if tickets:
             for ticket in tickets:
                 try:
                     comment = jira.add_comment(ticket,
                                                parsed_tweet['text'])
                 except:
                     print('add comment failed for ticket ', ticket.key)
             return True
         jiraSummary = 'Issue created by ' + full_tweet['user'][
             'screen_name']
         issue_dict = {
             'project': {
                 'key': 'MSD'
             },
             'summary': jiraSummary,
             'description': parsed_tweet['text'],
             'issuetype': {
                 'name': 'Problem'
             },
             'assignee': {
                 'name': 'admin'
             },
             'priority': {
                 'name': 'Medium'
             }
         }
         try:
             new_issue = jira.create_issue(fields=issue_dict)
         except Exception as e:
             print('Ticket creation failed for ',
                   full_tweet['user']['screen_name'])
             print(e)
             return True
         jira_data = {}
         jira_data['user'] = full_tweet['user']['screen_name']
         jira_data['ticket_key'] = new_issue.key
         jira_data['timestamp'] = str(datetime.now())
         db_jirainfo_result = db.jirainfo.insert_one(jira_data)
     #print(parsed_tweet['text'],',',parsed_tweet['sentiment'])
     return True
pullRequestUrl = 'https://github.com/jabong/' + repositoryName + '/pull/' + pullRequestId
jiraIssueUrl = jiraServer + '/browse/' + issueKey

strComment = 'h3. {color:green}Code Review @ revision ' + revisionNo + '{color}'
strComment += '\n{color:green}Code is OK @ revision [' + revisionNo + '|' + commitUrl + '] in GitHub{color}'
strComment += '\nBranch @ GitHub: ' + branchUrl
strComment += '\nPull Request can be seen at ' + pullRequestUrl

githubPullRequestCommentBody = 'Code is Ok @ Revision ' + revisionNo
githubPullRequestCommentBody += '\nJira Issue can be seen at ' + jiraIssueUrl

githubPullRequestMergeCommitLog = issueKey + ' ' + issueSummary

print "Comment to be added: \n" + strComment
print "================ Adding Comment ============================="
jira.add_comment(issue, strComment)

# Verify if the Comment was added successfully
updatedIssue = jira.issue(issueKey)
print "====== Comment Added: ========"
print updatedIssue.fields.comment.comments[updatedIssue.fields.comment.total -
                                           1].body

print "====== Issue Url: ========"
print jiraIssueUrl

print "====== Comment for Pull Request: ========"
print githubPullRequestCommentBody

#
#markAsReadyForQA = raw_input("Mark as Ready for QA? y/n: ")
Esempio n. 26
0
url = 'https://%s/ServerManage/RotatePassword' % tenant
headers = {
  'X-CENTRIFY-NATIVE-CLIENT': '1',
  'Authorization': 'Bearer %s' % cookie,
  'Content-Type': 'application/json'
}
r = requests.post(url, json = {
  "ID": _rowKey
}, headers = headers, verify = verify)
r.raise_for_status
responseObject = r.json()
print 'Rotating Password after Check-In: '
print ' '

print 'Closing JIRA Ticket'

options = {
  'server': jira_server
}

jira = JIRA(options, basic_auth = (jira_user, jira_password))

issue = jira.issue(issueID)
transitions = jira.transitions(issue)
print ' '
#print[(t['id'], t['name']) for t in transitions]# print[(t['id'], t['name']) for t in transitions]
comment = jira.add_comment(issueID, 'Password Checked In & Rotated - Closing Issue')
jira.transition_issue(issue, '31')
# EOF
Esempio n. 27
0
#options = {'server': 'http://localhost:8089'}
#jira = JIRA(options)
#jira = JIRA(basic_auth=('admin', 'admin'))
#issue = jira.issue('AIT-1')

import jira.client
from jira.client import JIRA

print['Going to fetch the issues for issue AIT-1']
options = {'server': 'http://localhost:8089'}
jira = JIRA(options, basic_auth=('admin', 'admin'))
issue = jira.issue('AIT-1')

print['/n################################################']
print['Going to fetch the COMMENTS for issue AIT-1']
comment = jira.add_comment(
    'AIT-1', 'new comment another one>?!')  # no Issue object required
comment = jira.add_comment(issue,
                           'new comment for now!!',
                           visibility={
                               'type': 'role',
                               'value': 'Administrators'
                           })  # for admins only

comment.update(body='updated comment body')
comment.delete()

print['/n################################################']
print['Going to fetch the TRANSITIONS for issue AIT-1']
issue = jira.issue('AIT-1')
transitions = jira.transitions(issue)
print[(t['id'], t['name']) for t in transitions]
Esempio n. 28
0
    # pprint.pprint(issue_dict)

    issues = jira.create_issue(fields=issue_dict)
    # issues=str(issues)

    # cursor.execute('''update ARC_OrderFormValues_old07072017
    # set jira_upload=? where ID=?''',str(issues),id1)
    # cnxn.commit()

    # cursor.execute(u,(issues,id1))
    issues.update(timetracking={
        'originalEstimate': estimate
    })  #this is the only way originalEstimate can be updated.

    # print (issues.raw)
    # pprint.pprint(issues.raw) #this is too many

    #add orignalestimate to dict
    #add wa
    # issues.update(fields={'timetracking': 'new summary', 'description': 'A new summary was added'})

    # # for i in issues:
    # #     print (i)
    # #add the person in the email to watcher here.
    # jira.add_watcher(issues.id, 'siddhesh.narkar')
    jira.add_watcher(issues.id, 'dinesh.jayapathy')
    jira.add_comment(issues.id, 'CR Created')
    # issue_dict.update({'watcher': watcher, 'originalEstimate': estimate,'comment':issues.fields.comment['comments']})
    pprint.pprint(issue_dict)
Esempio n. 29
0
                    # Only create the JIRA comment if this isn't an internal action to rt2jira
                    internal_action = 'Comments added by ' + config.get(
                        'rt', 'username')
                    new_comment = None
                    if internal_action not in c['Description']:
                        logger.info('Adding new comment to (' +
                                    jira_issue.key + ') from (' +
                                    comment_creator + ') on (' +
                                    rt_format_comment_time(comment_date) + ')')
                        syslog.syslog(
                            syslog.LOG_INFO,
                            'Adding new comment to (' + jira_issue.key +
                            ') from (' + comment_creator + ') on (' +
                            rt_format_comment_time(comment_date) + ')')
                        new_comment = jira.add_comment(jira_issue,
                                                       truncated_comment)

                    user = find_user(
                        comment_creator,
                        config.getint('jira', 'find_user_algo_type_comment'),
                        config.get('jira', 'find_user_projects'))
                    if user:
                        # Auto-add ticket commenter as watcher to the JIRA ticket.
                        logger.debug('Adding (' + user.name +
                                     ') as a watcher to (' + jira_issue.key +
                                     ')')
                        syslog.syslog(
                            syslog.LOG_DEBUG, 'Adding (' + user.name +
                            ') as a watcher to (' + jira_issue.key + ')')
                        jira.add_watcher(jira_issue, user.name)
                    else:
Esempio n. 30
0
class Housekeeping():
    """
    This class is the container for all automated Jira functions performed
    by the Housekeeping agent.

    """
    def __init__(self):
        # class variables
        self.ac_label =  u'auto-close-24-hours'
        # open JIRA API Connection
        self.jira = JIRA(options=secrets.options,
                            basic_auth=secrets.housekeeping_auth)

        # commands to run
        self.content_acquisition_auto_qc()
        self.requeue_free_indexing()
        self.auto_assign()
        self.remind_reporter_to_close()
        self.close_resolved()
        self.clear_auto_close_label()
        self.resolved_issue_audit()
        self.handle_audited_tickets()


    def content_acquisition_auto_qc(self):
        """
        Takes INDEXREP issues that have been Merged for 30+ minutes and
        transitions them to Quality Control. It then adds a comment that
        tags the reporter to inform them that the issue is ready for review.

        """
        # get CA tickets merged 30+ minute ago
        issues = self.get_issues("auto_qc")

        for issue in issues:
            reporter = issue.fields.reporter.key
            message = '[~%s], this issue is ready for QC.' % reporter
            """
            771 is the transition ID spedific to this step for this project.
            Anything more generic will need to parse the transitions list.
            """
            tran_id = self.get_transition_id(issue,"qc")
            self.jira.transition_issue(issue.key,tran_id)
            self.jira.add_comment(issue.key, message)

    def handle_audited_tickets(self):
        """
        Handles audit tickets that are failed. Closed tickets are ignored. Failed
        tickets trigger the creation of a new ticket in the same project as the
        original ticket.

        Inputs: None
        Returns: None

        """
        issues = self.jira.search_issues(   # get all the ADT issues
            'project=ADT and status="Failed Audit"')

        # For each failed issue, generate a new work ticket then close this one
        for issue in issues:
            #BUID
            adt_buid=issue.fields.customfield_10502
            #WCID
            adt_wcid=issue.fields.customfield_10501
            #oldBUID
            adt_old_buid=issue.fields.customfield_13100
            #oldWCID
            adt_old_wcid=issue.fields.customfield_13101
            #Indexing Type
            adt_indexing_type=issue.fields.customfield_10500
            #comments
            adt_comments = []
            for comment in self.jira.comments(issue):
                node = {
                    'body':self.jira.comment(issue,comment).body,
                    'author': self.jira.comment(issue,comment).author.key
                }
                adt_comments.append(node)

            link_list = [issue.key,] # first linked ticket should be this audit ticket
            for link in issue.fields.issuelinks: # grab the rest of links
                try:
                    link_list.append(link.outwardIssue.key)
                except AttributeError:
                    pass

            # capture original ticket and project. If it can't be found, default to INDEXREP as the project
            try:
                original_ticket = issue.fields.summary.split("[")[1].split("]")[0]
                original_project = original_ticket.split("-")[0]
            except IndexError:
                original_ticket = ""
                original_project="INDEXREP"

            # build the new summary by parsing the audit summary
            indexrep_summary = issue.fields.summary #build the summary
            indexrep_summary = indexrep_summary.replace("compliance audit - ","")
            indexrep_summary = indexrep_summary.split("[")[0]
            indexrep_summary = ' %s - Failed Audit' % (indexrep_summary)

            # Build the issue description
            message = 'This issue failed audit. Please review %s and make any \
                necessary corrections.' % original_ticket

            # Construct the watcher list and de-dupe it
            watcher_list = [issue.fields.assignee.key,]
            for w in self.jira.watchers(issue).watchers:
                watcher_list.append(w.key)
            watcher_list = set(watcher_list)

            # get the reporter (reporter is preserved from audit to issue)
            reporter = issue.fields.reporter.key

            # Generate the new issue, then close the audit ticket.
            new_issue = self.make_new_issue(original_project,"",reporter,
                                                indexrep_summary,message,
                                                watcher_list,link_list,adt_buid,
                                                adt_wcid,adt_old_buid,adt_old_wcid,
                                                adt_indexing_type,adt_comments)
            close_me = self.close_issue(issue.key)


    def resolved_issue_audit(self):
        """
        TAKES issues that have been resolved from specified projectsfor a set
        interval and creates a new ticket in AUDIT, closes the INDEXREP ticket,
        and then assigns it to the audit user specified in the self.qa_auditor role.

        Inputs: None
        Returns:    Error message or Nothing

        """
        # get all the issues from projects in the audit list
        issues = self.get_issues("audit_list")

        #build a list of all users in the MS & MD groups
        member_svc = self.get_group_members("member-services")
        member_dev = self.get_group_members("membership-development")
        member_aud = self.get_group_members("issue audits")
        member_all = []
        for user in member_svc:
            member_all.append(user) #only need the user names, not the meta data
        for user in member_dev:
            member_all.append(user)
        for user in member_aud:
            member_all.append(user)
        member_all = set(member_all) #de-dupe


        # cycle through them and create a new ADT ticket for each
        for issue in issues:
            # capture issue fields
            ind_buid=issue.fields.customfield_10502 #BUID
            ind_wcid=issue.fields.customfield_10501 #WCID
            ind_old_buid=issue.fields.customfield_13100 #oldBUID
            ind_old_wcid=issue.fields.customfield_13101 #oldWCID
            ind_indexing_type=issue.fields.customfield_10500 #Indexing Type
            reporter = issue.fields.reporter.key #Reporter

            link_list = [issue.key,]
            for link in issue.fields.issuelinks: # grab the rest of links
                try:
                    link_list.append(link.outwardIssue.key)
                except AttributeError:
                    pass
            # build the new ticket summary based on the issue being audited
            # [ISSUE=123] is used to preserve the original issue key. Replace any brackets with ()
            # to prevent read errors later.
            adt_summary = issue.fields.summary.replace("[","(").replace("]",")")
            adt_summary = 'compliance audit - %s [%s]' % (adt_summary,issue.key)

            # check reporter to see if special consideration is needed
            # if reporter is not MS or MD, or it's a new member, assign to audit lead.
            new_member_setup = self.check_for_text(issue,
                                                   settings.member_setup_strs)
            assigned_audit_tasks_query = self.get_issues("assigned_audits",True)
            if reporter not in member_all or new_member_setup:
                qa_auditor = self.user_with_fewest_issues('issue audits lead',
                                                      assigned_audit_tasks_query,
                                                      [reporter])
            else:
                # get the users who can be assigned audit tickets, then select the
                # one with fewest assigned tickets
                qa_auditor = self.user_with_fewest_issues('issue audits',
                                                          assigned_audit_tasks_query,
                                                          [reporter])

            # build the description
            message = '[~%s], issue %s is ready to audit.' % (qa_auditor, issue.key)

            #build the watcher list, including original reporter and assignee of the audited ticket
            watcher_list = []
            for w in self.jira.watchers(issue).watchers:
                watcher_list.append(w.key)

            try:
                original_assignee = issue.fields.assignee.key
            except AttributeError:
                original_assignee=""

            # make the audit ticket
            new_issue = self.make_new_issue("ADT",qa_auditor,reporter,
                adt_summary,message,watcher_list,link_list,ind_buid,
                ind_wcid,ind_old_buid,ind_old_wcid,ind_indexing_type)

            # close the INDEXREP ticket
            close_me = self.close_issue(issue.key)

            # add comment to indexrep ticket
            link_back_comment = "This issue has been closed. The audit ticket is %s" % new_issue
            self.jira.add_comment(issue.key, link_back_comment)


    def requeue_free_indexing(self):
        """
        Takes a list of old FCA tickets, and clears the assignee fields in order to
        allow it to be reassigned.

        Inputs: None
        Returns: Nothing

        """
        # get issues that are stale and need reassigned
        issues = self.get_issues("stale_free")

        # itirate issues and set assignee to empty. This will allow
        # auto assignment to set the assignee.
        for issue in issues:
            #check for wait in label
            wait_label = self.label_contains(issue,"wait")
            # if no wait label, clear the assignee so it can be re-autoassigned
            if (not wait_label):
                issue.update(assignee={'name':""})


    def make_new_issue(self,project,issue_assignee,issue_reporter,summary,
                                      description="",watchers=[],links=[],
                                      buid="",wcid="",old_buid="",old_wcid="",
                                      indexing_type="",comments=[],
                                      issuetype="Task"):
        """
        Creates a new issue with the given parameters.
        Inputs:
        *REQUIRED*
            :project:   the jira project key in which to create the issue
            :issue_assignee:    user name who the issue will be assigned to
            :issue_reporter:    user name of the issue report
            :summary:   string value of the issue summary field
            *OPTIONAL*
            :description: Issue description. Defaults to empty string
            :watchers: list of user names to add as issue watchers
            :link:  list of issue keys to link to the issue as "Related To"
            :issuetype: the type of issue to create. Defaults to type.
            :buid: business unit - custom field 10502
            :wcid: wrapping company id - custom field 10501
            :old_buid: old business unit - custom field 13100
            :old_wcid: old wrapping company id - custom field 13101
            :indexing_type: the indexing type - custom field 10500
            :comments: list dictionaries of comments and authors to auto add.
        Returns: Jira Issue Object

        """
        issue_dict = {
            'project':{'key':project},
            'summary': summary,
            'issuetype': {'name':issuetype},
            'description':description,
            }
        new_issue = self.jira.create_issue(fields=issue_dict)

        # assign the audit tick to auditor
        new_issue.update(assignee={'name':issue_assignee})
        new_issue.update(reporter={'name':issue_reporter})

        # add watchers to audit ticket (reporter, assignee, wacthers from indexrep ticket)
        for watcher in watchers:
            try:
                self.jira.add_watcher(new_issue,watcher)
            except:
                pass

        # link the audit ticket back to indexrep ticket
        for link in links:
            self.jira.create_issue_link('Relates',new_issue,link)

        # add custom field values if set
        if buid:
            new_issue.update(fields={'customfield_10502':buid})
        if wcid:
            new_issue.update(fields={'customfield_10501':wcid})
        if indexing_type:
            new_issue.update(fields={'customfield_10500':{'value':indexing_type.value}})
        if old_buid:
            new_issue.update(fields={'customfield_13100':old_buid})
        if old_wcid:
            new_issue.update(fields={'customfield_13101':old_wcid})

        # add comments
        quoted_comments = ""
        for comment in comments:
            quoted_comments = "%s[~%s] Said:{quote}%s{quote}\\\ \\\ " % (quoted_comments,comment['author'],comment['body'])

        if quoted_comments:
            quoted_comments = "Comments from the parent issue:\\\ %s" % quoted_comments
            self.jira.add_comment(new_issue,quoted_comments)

        return new_issue

    # method to transistion audit ticket
    def get_group_members(self, group_name):
        """
        Returns the members of a group as a list
        """
        group = self.jira.groups(query=group_name)[0]
        members = self.jira.group_members(group)
        return members

    def auto_assign(self,project="INDEXREP"):
        """
        Looks up new INDEXREP issues with an empty assignee and non-agent
        reporter and assigns them to the user in the content-acquisition user
        group with the fewest assigned contect-acquistion tickets.

        """
        # get member INDEXREP issues that need to auto assigned
        mem_issues = self.get_issues("member_auto_assign")
        # get free indexing requests
        free_issues = self.get_issues("free_auto_assign")
        # get unassigned member engagement issues
        mer_issues = self.get_issues("mer_auto_assign")
        # get unassigned sales engineering issues
        se_issues = self.get_issues("se_auto_assign")

        # get non-resolved assigned Member issues
        member_assigned_issues_query = self.get_issues("member_assigned_issues",True)
        # get non-resolved assigned Free Indexing issues
        free_assigned_issues_query = self.get_issues("free_assigned_issues",True)
        # get non-resolved member enagement issues
        mer_assigned_issues_query = self.get_issues("mer_assigned_issues",True)
        # get non-resolved sales engineering issues
        se_assigned_issues_query = self.get_issues("se_assigned_issues",True)

        def _assign(issue,username):
            """
            Private method for assigning an issue.
            Inputs:
            issue: issue to assign
            username: person to assign the issue

            """
            reporter = issue.fields.reporter.key
            self.jira.assign_issue(issue=issue,assignee=username)

            message = ("[~%s], this issue has been automically assigned "
                "to [~%s].") % (reporter,username)
            self.jira.add_comment(issue.key, message)

        auto_assign_dicts = [
            {
                "issue_list": mem_issues,
                "assigned_list": member_assigned_issues_query,
                "assignee_group": "content-acquisition",
            },
            {
                "issue_list": free_issues,
                "assigned_list": free_assigned_issues_query,
                "assignee_group": "content-acquisition-free",
            },
            {
                "issue_list": mer_issues,
                "assigned_list": mer_assigned_issues_query,
                "assignee_group": "mer-assignees",
                "watch_list":"mer-auto-watch",
            },
            {
                "issue_list": se_issues,
                "assigned_list": se_assigned_issues_query,
                "assignee_group": "se-assignees",
            }]

        for auto_assign_dict in auto_assign_dicts:
            for issue in auto_assign_dict["issue_list"]:
                username = self.user_with_fewest_issues(auto_assign_dict["assignee_group"],
                                                        auto_assign_dict["assigned_list"])


                if (auto_assign_dict["issue_list"]==mem_issues or
                    auto_assign_dict["issue_list"]==free_issues):
                    # check if the indexing type is already set. If so, do nothing.
                    if issue.fields.customfield_10500 == None:
                        # default to member indexing for issues in mem_issues
                        if auto_assign_dict["issue_list"]==mem_issues:
                            issue.update({"customfield_10500":{"id":"10103"}})

                        elif auto_assign_dict["issue_list"]==free_issues:
                            free_index_mem = self.get_group_members("free-index-default")
                            # set the indexing type to free if the reporter is in the list
                            # of users who default to free
                            if issue.fields.reporter.key in free_index_mem:
                                issue.update({"customfield_10500":{"id":"10100"}}) #free
                            else: #default is member otherwise
                                issue.update({"customfield_10500":{"id":"10103"}})

                # if the dict object has a watch list item, add default watchers
                if "watch_list" in auto_assign_dict:
                    watchers = self.get_group_members(auto_assign_dict["watch_list"])
                    self.toggle_watchers("add",issue,watchers)

                _assign(issue,username)


    def remind_reporter_to_close(self):
        """
        Comments on all non-closed resolved issues that are 13 days without a
        change. Notifies the reporter it will be closed in 24 hours and adds a
        label to the issue that is used as a lookup key by the close method.

        """
        issues = self.get_issues("remind_close_issues")
        for issue in issues:
            reporter = issue.fields.reporter.key
            message = (
                "[~%s], this issue has been resolved for 13 days. It will be "
                "closed automatically in 24 hours.") % reporter
            watch_list = self.toggle_watchers("remove",issue)
            self.jira.add_comment(issue.key,message)
            issue.fields.labels.append(self.ac_label)
            issue.update(fields={"labels": issue.fields.labels})
            self.toggle_watchers("add",issue, watch_list)

    def close_resolved(self):
        """
        Looks up all issues labeled for auto-closing that have not been updated
        in 24 hours and closes them. Ignores INDEXREP so as to not interfere
        with the auditing process.

        """
        issues = self.get_issues("auto_close_issues")
        for issue in issues:
            reporter = issue.fields.reporter.key
            message = (
                "[~%s], this issue has closed automatically.") % reporter
            close_me = self.close_issue(issue)
            self.jira.add_comment(issue.key,message)

    def get_transition_id(self,issue,key):
        """
        Finds the transition id for an issue given a specific search string.
        Inputs:
            key: search string
            issue: jira issue
        Returns: transition id or False

        """
        trans = self.jira.transitions(issue)
        tran_id = False
        for tran in trans:
            tran_name = tran['name'].lower()
            if key in tran_name:
                tran_id = tran['id']
        return tran_id

    def close_issue(self, issue):
        """
        Closes the issue passed to it with a resolution of fixed.
        Inputs: Issue: the issue object to close
        Returns: True|False

        """
        trans = self.jira.transitions(issue)
        success_flag = False
        tran_id = self.get_transition_id(issue,"close")
        if not tran_id:
            tran_id = self.get_transition_id(issue,"complete")

        if tran_id:
            try:
                self.jira.transition_issue(issue,tran_id,
                                           {'resolution':{'id':'1'}})
            #some close transitions don't have a resolution screen
            except: #open ended, but the JIRAError exception is broken.
                self.jira.transition_issue(issue,tran_id)
            success_flag = True
        return success_flag

    def clear_auto_close_label(self):
        """
        Clears the auto-close label from issues that have been re-opened
        since the auto-close reminder was posted.

        """
        issues = self.jira.search_issues(
            'status in ("Quality Control", Reopened, Merged, open) \
            AND labels in (auto-close-24-hours)')
        for issue in issues:
            label_list =  issue.fields.labels
            watch_list = self.toggle_watchers("remove",issue)
            label_list.remove(self.ac_label)
            issue.update(fields={"labels": label_list})
            self.toggle_watchers("add",issue, watch_list)


    def toggle_watchers(self,action,issue,watch_list=[]):
        """
        Internal method that either adds or removes the watchers of an issue. If
        it removes them,it returns a list of users that were removed. If it
        adds, it returns an updated list of watchers.

        Inputs:
        :action: String "add"|"remove". The action to take
        :issue:  Issue whose watchers list is being modified
        :watch_list: list of users. Optional for remove. Required for add.

        Returns:
        :issue_watcher: List of users who are or were watching the issue.

        """
        if action=="remove":
            issue_watchers = self.jira.watchers(issue).watchers
            for issue_watcher in issue_watchers:
                # watch list can be inconsensent when returned by the jira api
                # same issue in the add loop
                try:
                    self.jira.remove_watcher(issue,issue_watcher.name)
                except AttributeError:
                    self.jira.add_watcher(issue,old_watcher)
        else:
            for old_watcher in watch_list:
                try:
                    self.jira.add_watcher(issue,old_watcher.name)
                except AttributeError:
                    self.jira.add_watcher(issue,old_watcher)
            issue_watchers = self.jira.watchers(issue).watchers
        return issue_watchers

    def label_contains(self,issue,search_string):
        """
        Internal method that searches the labels of an issue for a given string
        value. It allows filtering that is roughly "labels ~ 'string'", which
        is not supported by JQL.

        Inputs:
        :issue: Jira issue object that is being checked
        :search_string: the string value being checked for

        Returns:
        True|False  True if search_string exists in any label.

        """
        return any(search_string in label for label in issue.fields.labels)


    def check_for_text(self,issue,text_list):
        """
        Internal method that searches the summary and description of an issue for
        a given list of strings. Match is non-case sensative, and converts
        everything to lower case before checking for a match.

        Inputs:
        :issue: Jira issue object that is being checked
        :text_list: strings to checked for

        Returns:
        True|False  True if any of the values in text_list exist.

        """
        string_exists = False
        if issue.fields.summary:
            summary = issue.fields.summary.lower()
        else:
            summary = ""

        if issue.fields.description:
            description = issue.fields.description.lower()
        else:
            description = ""

        for text in text_list:
            text = text.lower()
            if text in summary or text in description:
                string_exists = True

        return string_exists


    def user_with_fewest_issues(self,group,query,blacklist=[]):
        """
        Given a query, return the username of the use with the fewest assigned
        issues in the result set.

        Inputs:
        group: the group of users for which to count issues.
        query: the issues to lookup. Should be a JQL string.
        blacklist: (optional) list of inelligible users

        """
        members = self.get_group_members(group)
        issues = self.jira.search_issues(query,maxResults=1000)

        member_count = {}

        for member in members:
            member_count[member]=0

        # perform the count anew for each ticket
        for issue in issues:
            if issue.fields.assignee:
                assignee = issue.fields.assignee.key
            else:
                assignee = None
            if assignee in members and not self.label_contains(issue,"wait"):
                member_count[assignee] = member_count[assignee]+1

        #sort the list so that the user with the lowest count is first
        member_count_sorted = sorted(member_count.items(),
            key=operator.itemgetter(1))

        # prevent assignment to a user in the blacklist, so long as there are
        # at least 2 available users
        while (str(member_count_sorted[0][0]) in blacklist and
            len(member_count_sorted)>1):
            del member_count_sorted[0]

        # return the username of the user
        return str(member_count_sorted[0][0])


    def get_issues(self,filter_key,return_jql=False):
        """
        Returns issues found using a jira filter.

        Inputs:
            :filter_key:    the dict key for the filter in settings
            :return_jql:    flag to return JQL instead on issues

        Returns:
            :issues:    Jira Issues object (default) or JQL string

        """
        filter_id = secrets.jira_filters[filter_key]
        jql_query = self.jira.filter(filter_id).jql
        if return_jql:
            # some functionality needs the JQL instead of an issue list
            # notably the method self.user_with_fewest_issues
            return jql_query
        else:
            issues = self.jira.search_issues(jql_query)
            return issues
Esempio n. 31
0
# By default, the client will connect to a JIRA instance started from the Atlassian Plugin SDK
# (see https://developer.atlassian.com/display/DOCS/Installing+the+Atlassian+Plugin+SDK for details).
# Override this with the options parameter.
options = {
    'server': 'https://jira.atlassian.com'
}
jira = JIRA(options)

# Get all projects viewable by anonymous users.
projects = jira.projects()

# Sort available project keys, then return the third, fourth and fifth keys.
keys = sorted([project.key for project in projects])[2:5]

# Get an issue.
issue = jira.issue('JRA-1330')

# Find all comments made by Atlassians on this issue.
import re
atl_comments = [comment for comment in issue.fields.comment.comments
                if re.search(r'@atlassian.com$', comment.author.emailAddress)]

# Add a comment to the issue.
jira.add_comment(issue, 'Comment text')

# Change the issue's summary and description.
issue.update(summary="I'm different!", description='Changed the summary to be different.')

# Send the issue away for good.
issue.delete()
Esempio n. 32
0
class JIRAReporter(IActionBase, TargetableAction):
    implements(IAction)

    id = 'JIRAReporter'
    name = 'JIRA Issue Reporter'
    actionContentInfo = IJIRAActionContentInfo

    shouldExecuteInBatch = False

    def __init__(self):
        log.debug('[research] %s : initialized' % (self.id))
        super(JIRAReporter, self).__init__()

        self.connected = False
        self.jira = None

    def setupAction(self, dmd):
        log.debug('[research] setup : %s' % (self.name))
        self.guidManager = GUIDManager(dmd)
        self.dmd = dmd

    def executeOnTarget(self, notification, signal, target):
        self.setupAction(notification.dmd)

        log.debug('[research] execute : %s on %s' % (self.name, target))

        jiraURL = notification.content['jira_instance']
        jiraUser = notification.content['jira_user']
        jiraPass = notification.content['jira_password']

        issueProject = notification.content['issue_project']
        issueType = notification.content['issue_type']
        issuePriority = notification.content['issue_priority_key']
        customfields = notification.content['customfield_keypairs']
        eventRawData = notification.content['event_rawdata']
        serviceRoot = notification.content['service_group_root']

        summary = ''
        description = ''

        if (signal.clear):
            log.info('[research] event cleared : %s' % (target))
            description = notification.content['clear_issue_description']
        else:
            log.warn('[research] event detected : %s' % (target))
            summary = notification.content['issue_summary']
            description = notification.content['issue_description']

        actor = signal.event.occurrence[0].actor
        device = None
        if (actor.element_uuid):
            device = self.guidManager.getObject(actor.element_uuid)

        component = None
        if (actor.element_sub_uuid):
            component = self.guidManager.getObject(actor.element_sub_uuid)

        environ = {
            'dev': device,
            'component': component,
            'dmd': notification.dmd
        }

        data = _signalToContextDict(signal, self.options.get('zopeurl'),
                                    notification, self.guidManager)

        environ.update(data)

        if (environ.get('evt', None)):
            environ['evt'] = self._escapeEvent(environ['evt'])

        if (environ.get('clearEvt', None)):
            environ['clearEvt'] = self._escapeEvent(environ['clearEvt'])

        environ['user'] = getattr(self.dmd.ZenUsers, target, None)

        issueValues = {
            'summary': summary,
            'description': description,
            'eventraw': eventRawData,
            'customfields': customfields
        }

        log.debug('[research] base issue values : %s' % (issueValues))

        targetValues = {
            'project': issueProject,
            'issuetype': issueType,
            'priority': issuePriority,
            'serviceroot': serviceRoot
        }

        log.debug('[research] base target values : %s' % (targetValues))

        self.connectJIRA(jiraURL, jiraUser, jiraPass)

        if (signal.clear):
            self.clearEventIssue(environ, targetValues, issueValues)
        else:
            self.createEventIssue(environ, targetValues, issueValues)

        log.debug("[research] event update reported : %s" % (jiraURL))

    def getActionableTargets(self, target):
        ids = [target.id]
        if isinstance(target, GroupSettings):
            ids = [x.id for x in target.getMemberUserSettings()]
        return ids

    def _escapeEvent(self, evt):
        """
        Escapes the relavent fields of an event context for event commands.
        """
        if evt.message:
            evt.message = self._wrapInQuotes(evt.message)
        if evt.summary:
            evt.summary = self._wrapInQuotes(evt.summary)
        return evt

    def _wrapInQuotes(self, msg):
        """
        Wraps the message in quotes, escaping any existing quote.

        Before:  How do you pronounce "Zenoss"?
        After:  "How do you pronounce \"Zenoss\"?"
        """
        QUOTE = '"'
        BACKSLASH = '\\'
        return ''.join((QUOTE, msg.replace(QUOTE, BACKSLASH + QUOTE), QUOTE))

    def updateContent(self, content=None, data=None):
        super(JIRAReporter, self).updateContent(content, data)

        updates = dict()
        properties = [
            'jira_instance', 'jira_user', 'jira_password', 'issue_project',
            'issue_type', 'issue_priority_key', 'issue_summary',
            'issue_description', 'clear_issue_summary', 'customfield_keypairs',
            'event_rawdata', 'service_group_root'
        ]

        for k in properties:
            updates[k] = data.get(k)

        content.update(updates)

# jira client methods

    def connectJIRA(self, URL, user, password):
        log.debug('[research] : connecting to %s' % (URL))

        basicauth = (user, password)

        try:
            self.jira = JIRA(options={'server': URL}, basic_auth=basicauth)
            self.connected = True
            log.debug('[research] : connected to %s' % (URL))
        except JIRAError as jx:
            log.error('[research] jira.error : %s' % (jx))
        except Exception as ex:
            log.debug('[research] exception : %s' % (ex))
        finally:
            log.debug('[research] connection info (%s)' % (URL))

    def setIssueValues(self, data, targetValues, issueValues):
        log.debug('[research] process issue values')

        if ('project' in targetValues):
            issueValues['project'] = {'key': targetValues['project']}
        if ('issuetype' in targetValues):
            issueValues['issuetype'] = {'name': targetValues['issuetype']}

        issueValues['summary'] = self.processEventFields(
            data, issueValues['summary'], 'Summary')
        issueValues['description'] = self.processEventFields(
            data, issueValues['description'], 'Description')

        log.debug('[research] issue values : %s' % (issueValues))

        return issueValues

    def setCustomFieldValues(self, data, targetValues, issueValues):
        log.debug('[research] process customfield values')

        customfields = None
        if ('customfields' in issueValues):
            customfields = issueValues['customfields']
            del issueValues['customfields']

        if (customfields):
            customfields = json.loads(customfields)
        else:
            customfields = {}

        if ('priority' in targetValues):
            customfields['Priority'] = targetValues['priority']

        if ('serviceroot' in targetValues):
            customfields['Service'] = targetValues['serviceroot']

        if ('eventraw' in issueValues):
            customfields['Zenoss EventRAW'] = self.processEventFields(
                data, issueValues['eventraw'], 'Event Raw Data')
            del issueValues['eventraw']

        customfields = self.setZenossFields(data, customfields)

        log.debug('[research] customfield values : %s' % (customfields))

        createmeta = self.jira.createmeta(
            projectKeys=targetValues['project'],
            issuetypeNames=targetValues['issuetype'],
            expand='projects.issuetypes.fields')

        issuetype = None
        fields = None
        if (createmeta):
            if ('projects' in createmeta):
                if ('issuetypes' in createmeta['projects'][0]):
                    issuetype = createmeta['projects'][0]['issuetypes'][0]
                    log.debug('[research] createmeta issuetype : available')
            if (issuetype):
                if ('fields' in issuetype):
                    fields = issuetype['fields']
                    log.debug('[research] createmeta fields : available')
        else:
            log.debug('[research] createmeta : NOT AVAILABLE')

        if (fields):
            for fKey, fAttr in fields.iteritems():
                if ('name' in fAttr):
                    if (fAttr['name'] in customfields):
                        log.debug('[research] customfield found')
                        fieldValue = customfields[fAttr['name']]
                        if ('allowedValues' in fAttr):
                            log.debug('[research] has customfield options')
                            fieldValue = self.getCustomFieldOption(
                                fAttr['allowedValues'], fieldValue)
                        if (fieldValue):
                            log.debug('[research] cf (%s) set to %s' %
                                      (fAttr['name'], fieldValue))
                            try:
                                if (fAttr['schema']['type'] in ['array']):
                                    fieldValue = [fieldValue]
                            except:
                                pass
                            issueValues[fKey] = fieldValue

        log.debug('[research] issue customfields : %s' % (issueValues))

        return issueValues

    def setZenossFields(self, data, customfields):
        log.debug('[research] process customfield values')

        if (not customfields):
            customfields = {}

        zEventID = self.getEventID(data)
        if (zEventID):
            customfields['Zenoss ID'] = zEventID

        zDeviceID = self.getDeviceID(data)
        if (zDeviceID):
            customfields['Zenoss DevID'] = zDeviceID

        zBaseURL = self.getBaseURL(data)
        if (zBaseURL):
            customfields['Zenoss Instance'] = zBaseURL

        zEnv = self.getEnvironment(data)
        if (zEnv):
            customfields['Environment'] = zEnv

        if ('Service' in customfields):
            zSrvc = self.getServiceGroup(data, customfields['Service'])
            if (zSrvc):
                customfields['Service'] = zSrvc

        zLoc = self.getLocation(data)
        if (zLoc):
            customfields['DataCenter'] = zLoc

        log.debug('[research] Zenoss customfields : %s' % (customfields))

        return customfields

    def createEventIssue(self, data, targetValues, issueValues):
        log.debug('[research] create event issue')

        project = targetValues['project']

        eventID = self.getEventID(data)
        baseHost = self.getBaseHost(data)
        deviceID = self.getDeviceID(data)

        hasIssues = self.hasEventIssues(project, baseHost, eventID)

        if (hasIssues):
            log.warn('[research] issue exists for EventID %s' % (eventID))
        else:
            issueValues = self.setIssueValues(data, targetValues, issueValues)
            issueValues = self.setCustomFieldValues(data, targetValues,
                                                    issueValues)

            newissue = self.jira.create_issue(fields=issueValues)
            log.info('[research] issue created : %s' % (newissue.key))

    def clearEventIssue(self, data, targetValues, issueValues):
        log.debug('[research] clear event issue')

        project = targetValues['project']

        eventID = self.getEventID(data)
        baseHost = self.getBaseHost(data)

        issues = self.getEventIssues(project, baseHost, eventID)

        if (not issues):
            log.warn('[research] no issue mapped to clear : %s' % (eventID))
            return

        issueValues = self.setIssueValues(data, targetValues, issueValues)

        description = issueValues['description']

        eventCLR = self.getEventClearDate(data)

        for issue in issues:
            zenossCLR = self.getCustomFieldID(issue, 'Zenoss EventCLR')
            issuekey = issue.key
            if (zenossCLR):
                issue.update(fields={zenossCLR: eventCLR})
                log.info('[research] EventCLR updated : %s' % (issuekey))
            if (description):
                self.jira.add_comment(issue.key, description)
                log.info('[research] EventCLR commented : %s' % (issuekey))

    def hasEventIssues(self, project, eventINS, eventID):
        log.debug('[research] has event issues')

        issues = self.getEventIssues(project, eventINS, eventID)

        log.debug('[research] has event issues : %s' % (len(issues) > 0))

        return (len(issues) > 0)

    def getEventIssues(self, project, eventINS, eventID):
        log.debug('[research] get event issues')

        issues = []

        if (eventID):
            issueFilter = '(project = "%s")'
            issueFilter += ' and ("Zenoss Instance" ~ "%s")'
            issueFilter += ' and ("Zenoss ID" ~ "%s")'
            issueFilter = issueFilter % (project, eventINS, eventID)
            log.debug('[research] event issue filter : %s' % (issueFilter))

            try:
                issues = self.jira.search_issues(issueFilter)
                log.debug('[research] event issues : %s' % (len(issues)))
            except JIRAError as jx:
                log.error('[research] jira.error : %s' % (jx))
            except Exception as ex:
                log.error('[research] exception : %s' % (ex))

        return issues

    def getCustomFieldOption(self,
                             fieldOptions,
                             value,
                             defaultValue='',
                             exactMatch=False,
                             firstMatch=False):
        log.debug('[research] get customfield options')

        if (not value):
            return None

        if (not fieldOptions):
            return None

        bDefault = False
        matchValue = None

        if (value.__class__.__name__ in ('str', 'unicode')):
            value = value.split(';')
            if (len(value) > 1):
                defaultValue = value[1].strip()
                log.debug('[research] option default : %s' % (defaultValue))
            value = value[0].strip()

        if (not value):
            log.debug('[research] invalid option value : %s' % (value))

        for av in fieldOptions:
            if ('value' in av):
                valueName = av['value']
            elif ('name' in av):
                valueName = av['name']
            else:
                continue

            if (value):
                if (value.__class__.__name__ in ('str', 'unicode')):
                    if (exactMatch):
                        value = '^%s$' % (value)
                    if (re.match(value, valueName, re.IGNORECASE)):
                        if ('id' in av):
                            matchValue = {'id': av['id']}
                        else:
                            matchValue = valueName
                        if (firstMatch):
                            break

            if (not defaultValue):
                continue

            if (defaultValue.__class__.__name__ in ('str', 'unicode')):
                if (re.match(defaultValue, valueName, re.IGNORECASE)):
                    bDefault = True
                    if ('id' in av):
                        defaultValue = {'id': av['id']}
                    else:
                        defaultValue = valueName
                    if (not value):
                        break

        if (not matchValue):
            if (bDefault):
                log.debug('[research] default option : %s' % (defaultValue))
                matchValue = defaultValue

        return matchValue

    def getCustomFieldID(self, issue, fieldName):
        log.debug('[research] get issue customfield ID')

        fieldID = ''

        for field in self.jira.fields():
            if (field['name'].lower() == fieldName.lower()):
                log.debug('[research] customfield matched %s' % (fieldName))
                fieldID = field['id']
                break

        return fieldID

    def getEventID(self, data):
        log.debug('[research] get eventID')

        eventID = '${evt/evid}'
        try:
            eventID = self.processEventFields(data, eventID, 'eventID')
        except Exception:
            eventID = ''

        return eventID

    def getEventClearDate(self, data):
        log.debug('[research] get event clear date')

        eventCLR = '${evt/stateChange}'
        try:
            eventCLR = self.processEventFields(data, eventCLR, 'clear date')
        except Exception:
            eventCLR = ''

        if (eventCLR):
            try:
                eventCLR = datatime.strptime(
                    eventCLR,
                    '%Y-%m-%d %H:%M:%S').isoformat()[:19] + '.000+0000'
            except:
                try:
                    eventCLR = datatime.strptime(
                        eventCLR,
                        '%Y-%m-%d %H:%M:%S.%f').isoformat()[:19] + '.000+0000'
                except:
                    eventCLR = ''

        if (not eventCLR):
            eventCLR = datetime.now().isoformat()[:19] + '.000+0000'

        return eventCLR

    def getDeviceID(self, data):
        log.debug('[research] get deviceID')

        deviceID = '${evt/device}'
        try:
            deviceID = self.processEventFields(data, deviceID, 'deviceID')
        except Exception:
            deviceID = ''

        return deviceID

    def getBaseURL(self, data):
        log.debug('[research] get baseURL')

        baseURL = '${urls/baseUrl}'
        try:
            baseURL = self.processEventFields(data, baseURL, 'baseURL')
        except Exception:
            baseURL = ''

        if (baseURL):
            baseURL = self.getSiteURI(baseURL)

        return baseURL

    def getBaseHost(self, data):
        log.debug('[research] get baseHost')

        baseHost = ''

        baseHost = self.getBaseURL(data)

        return urlparse(baseHost).hostname

    def getEnvironment(self, data):
        log.debug('[research] get environment')

        eventENV = '${dev/getProductionStateString}'
        try:
            eventENV = self.processEventFields(data, eventENV,
                                               'Event ENV (dev)')
        except Exception:
            eventENV = '${evt/prodState}'
            try:
                eventENV = self.processEventFields(data, eventENV,
                                                   'Event ENV (evt)')
            except Exception:
                eventENV = ''

        return eventENV

    def getServiceGroup(self, data, valuePattern):
        log.debug('[research] get service group')

        srvcGRP = '${evt/DeviceGroups}'
        try:
            srvcGRP = self.processEventFields(data, srvcGRP, 'Service')
            srvcGRP = srvcGRP.split('|')
        except Exception:
            srvcGRP = []

        extendGRP = []
        defaultGRP = None

        valuePattern = valuePattern.split(';')
        if (len(valuePattern) > 1):
            defaultGRP = valuePattern[1].strip()
        valuePattern = valuePattern[0].strip()

        if (valuePattern):
            for ix in range(len(srvcGRP)):
                svcm = re.match(valuePattern, srvcGRP[ix], re.IGNORECASE)
                if (svcm):
                    valGRP = svcm.group(2)
                    if (valGRP):
                        valGRP = valGRP.split('/')
                        for ex in range(len(valGRP)):
                            extendGRP.append('\(' + '/'.join(valGRP[:ex + 1]) +
                                             '\)')

        log.debug('[research] service group patterns : %s' % (extendGRP))

        if (extendGRP):
            srvcGRP = '.*(' + '|'.join(extendGRP) + ').*'
        else:
            srvcGRP = ''

        if (defaultGRP):
            srvcGRP += ';' + defaultGRP

        log.debug('[research] service pattern : %s' % (srvcGRP))

        return srvcGRP

    def getLocation(self, data):
        log.debug('[research] get location')

        loc = '${evt/Location}'
        try:
            loc = self.processEventFields(data, loc, 'Location')
        except Exception:
            loc = ''

        for locx in loc.split('/'):
            if (locx):
                return locx

        return loc

    def getSiteURI(self, source):
        outURI = re.findall("((http|https)://[a-zA-Z0-9-\.:]*)", source)
        if (outURI.__class__.__name__ in ['list']):
            if (len(outURI) > 0):
                if (len(outURI[0]) > 0):
                    outURI = outURI[0][0]
        log.debug('[research] zenoss URL : %s' % (outURI))
        outURI = urlparse(source)
        return "%s://%s" % (outURI.scheme, outURI.netloc)

    def processEventFields(self, data, content, name):
        log.debug('[research] process TAL expressions')

        try:
            content = processTalSource(content, **data)
            log.debug('[research] %s : %s' % (name, content))
        except Exception:
            log.debug('[research] unable to process : %s' % (name))
            raise ActionExecutionException(
                '[research] failed to process TAL in %s' % (name))

        if (content == 'None'):
            content = ''

        return content

    def removeEmptyListElements(self, listObj):
        log.debug('[research] remove empty list elements')

        bDirty = True
        for lx in range(len(listObj)):
            try:
                ix = listObj.index('')
                listObj[ix:ix + 1] = []
            except Exception:
                bDirty = False

            try:
                ix = listObj.index()
                listObj[ix:ix + 1] = []
                if (not bDirty):
                    bDirty = True
            except Exception:
                if (not bDirty):
                    bDirty = False

            if (not bDirty):
                break

        return listObj

    def processServiceGroupUsingRoot(self, serviceGroups, rootPattern):
        log.debug('[research] filter service group values')

        return serviceGroups
Esempio n. 33
0
#coding=utf-8
# auther: yubb
from jira.client import JIRA
issue = ['MMMJ-32', 'MMMJ-36']
describe = '来自python的测试'
flow_id = '431'
jira = JIRA('http://jira.m.com/', basic_auth=('gitlab', '123kaibola'))
for issue_id in issue:
    jira.add_comment(issue_id, describe)
    transitions = jira.transitions(issue_id)
    print transitions
    print[(t['id'], t['name']) for t in transitions]
    jira.transition_issue(issue_id, flow_id)
    jira.add_simple_link(issue_id, {
        "url": "http://www.2339.com/",
        "title": "1234"
    })
# Verify all issues.
other_issues = {}
for issue in issues:
    other_issues[issue] = issues[:]
    other_issues[issue].remove(issue)
    jira.issue(issue)

# Add a comment if it's merged.
if event_type == "change-merged" and change_url:
    for issue in issues:
        # Append a very basic comment.
        good_article = get_article(good_adjective)
        body = "[~%s] has merged %s %s [change|%s]." % (
            jira_user, good_article, good_adjective, change_url)

        if len(other_issues[issue]):
            # Append a comment with other related issues. This will make a link.
            body = body + "\nRelated issues: " + ",".join(other_issues[issue])

        # Add the comment to JIRA.
        comment = jira.add_comment(issue, body)

# Exit with okay if there are any tokens or issues.
if len(issues) > 0 or len(tokens) > 0:
    print "This commit looks positively %s!" % good_adjective
    sys.exit(0)
else:
    print "This commit is a bit %s for my taste." % bad_adjective
    sys.exit(1)
Esempio n. 35
0
def main():
    settings = options()
    rally = pyral.Rally(settings["rally"]["server"],
                        settings["rally"]["username"],
                        settings["rally"]["password"],
                        project=settings["rally"]["project"])

    import_into_jira = settings["import_into_jira"]

    jira = None
    if import_into_jira:
        jira = JIRA(
            server=settings["jira"]["server"],
            basic_auth=(settings["jira"]["username"], settings["jira"]["password"])
        )

    stories = [rally.get(kind_of_story(story)[0], query='FormattedID = "{0}"'.format(story)).next() for story in settings["rally"]["stories"]]

    # Weep for me...
    for story in stories:
        if import_into_jira:
            issue = jira.create_issue(
                project={"key": settings["jira"]["project"]},
                summary=story.Name,
                description=html2confluence(story.Description),
                reporter={"name": settings["jira"]["on_behalf_of"]},
                issuetype={"name": kind_of_story(story.FormattedID)[1]}
            )
            header = "{0} -> {1}: {2}".format(story.FormattedID, issue.key, story.Name)
        else:
            header = "{0}: {1}".format(story.FormattedID, story.Name)

        print(header)

        names = []
        for attachment in story.Attachments:
            names.append(attachment.Name)

            if import_into_jira:
                tmp = tempfile.mkstemp()[1]

                with open(tmp, "wb") as fh:
                    fh.write(base64.b64decode(attachment.Content.Content))
                    fh.close()

                jira.add_attachment(issue.key, tmp, attachment.Name)
                os.remove(tmp)

        print("\tAttachments: " + ", ".join(names))

        print("\tDiscussion:")
        for ConversationPost in story.Discussion:
            print("\t\t{0}:".format(ConversationPost.User.Name))
            print("\n".join(["\t\t" + line for line in html2confluence(ConversationPost.Text).split("\n")]))

            if import_into_jira:
                jira.add_comment(issue=issue.key,
                                 body=html2confluence(
                                     "On behalf of {0}:<br/><br/>{1}".format(ConversationPost.User.Name,
                                                                             ConversationPost.Text)))

        # ...a little more
        for rally_task in story.Tasks:
            jira_task = None
            if import_into_jira:
                jira_task = jira.create_issue(
                    project={"key": settings["jira"]["project"]},
                    parent={"key": issue.key},
                    summary=rally_task.Name,
                    description=html2confluence(rally_task.Description),
                    reporter={"name": settings["jira"]["on_behalf_of"]},
                    issuetype={"id": "5"}
                )
                header = "\t{0} -> {1}: {2}".format(rally_task.FormattedID, jira_task.key, rally_task.Name)
            else:
                header = "\t{0}: {1}".format(rally_task.FormattedID, rally_task.Name)
            
            print(header)

            names = []
            for attachment in rally_task.Attachments:
                names.append(attachment.Name)

                if import_into_jira:
                    tmp = tempfile.mkstemp()[1]

                    with open(tmp, "wb") as fh:
                        fh.write(base64.b64decode(attachment.Content.Content))
                        fh.close()

                    jira.add_attachment(jira_task.key, tmp, attachment.Name)
                    os.remove(tmp)

            print("\t\tAttachments: " + ", ".join(names))

            print("\t\tDiscussion:")
            for ConversationPost in rally_task.Discussion:
                print("\t\t\t{0}:".format(ConversationPost.User.Name))
                print("\n".join(["\t\t\t" + line for line in html2confluence(ConversationPost.Text).split("\n")]))

                if import_into_jira:
                    jira.add_comment(issue=jira_task.key,
                                     body=html2confluence(
                                         "On behalf of {0}:<br/><br/>{1}".format(ConversationPost.User.Name,
                                                                                 ConversationPost.Text)))
Esempio n. 36
0
class JiraRobot:
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    jira = None

    def connect_to_jira(self, JIRAUsername=None,
                        JIRAPassword=None, options=None):
        """
        Connect to a JIRA server.

            Arguments:
                |  JIRAUsername (string)  	|  (Optional) A JIRA Username you wish to authenticate with, will authorise with anonymous account if left empty      				|
                |  JIRAPassword (string)  	|  (Optional) If a username was specified and a password is not the user will be prompted for password at runtime 					|
                |  options (dictionary) 	|  (Optional) A dictionary of options that the JIRA connection will be initialied with 	                                            |

            This must be called first to be able to do most JIRA actions such as creating a new issue and assigning users.
            When connecting to JIRA you may need to authenticate a user. This can be done by passing in Username and Password as parameters. However, should you wish to not have a sensitive password saved in a file,  an option is available to not pass in a Password and a prompt will appear asking for the JIRA password at runtime.


            'connect to jira' can be called on its own and will default options to:
                '{'rest_api_version': '2', 'verify': True,
                'server': 'http://*****:*****@ssword 						| {'server': http://devjira01'} 	|
        """

        if JIRAUsername is not None and (JIRAPassword is "" or JIRAPassword is None):
            JIRAPassword = getpass.getpass("\nJIRA Password: "******"\nAuthentication to JIRA unsuccessful. Ensure the user used has sufficient access and that Username and Password were correct\n\n")
                sys.exit(1)

        else:
            try:
                self.jira = JIRA(options=JIRAOptions, basic_auth=(str(JIRAUsername),
                                 str(JIRAPassword)))
            except:
                sys.__stdout__.write("\nAuthentication to JIRA unsuccessful. Ensure the user used has sufficient access and that Username and Password were correct\n\n")
                sys.exit(1)

    def create_issue(self, issue_field_dict, assign_current_user=False):
        """
        Creates a new JIRA issue.

            Arguments:
                |  issue_field_dict (string)  			| A dictionary in the form of a string that the user can specify the issues fields and field values 			|
                |  assign_current_user (string/bool)  	| (Optional) A flag to assign the current user to the issue once it is successfully created, defaults to False	|

            Will create a new issue and returns an Issue Key.
            The user will use the issue_field_dict variable to specify the issues field and their respective values. This must be in the form of a string written as a dictionary
                e.g. {'FieldName':'FieldValue'}

            The field value can also be another dictionary (in some field cases this is needed)
                e.g. {'FieldName1':{'key':'value'}, 'FieldName2':FieldValue, 'FieldName3':{'name':'value'}}

            This Create Issue Example page (https://developer.atlassian.com/display/JIRADEV/JIRA+REST+API+Example+-+Create+Issue) is full of useful information in what is required for creating issues, including customfields and issue field value types, it is very important to get the value type right or an error will be thrown.


            Examples:
                |  *Keyword*        	|  *Parameters*   	| 														| 		|
                |  ${issue_field_dict} 	|  {'project':{'key': 'PROJ'}, 'summary':'Create New Issue', 'description':'Creating a new issue', 'issuetype':{'name': 'Bug'}} |    |
                |  connect to jira      |  asimmons         | options= {'http://devjira01'}                         |       |
                |  ${issue}				|  create issue 	|  ${issue_field_dict}									|      	|

                |  connect to jira      |  asimmons         | options= {'http://devjira01'}                         |  		|
                |  ${issue}				|  create issue 	|  ${issue_field_dict}									|  True |
        """
        issue_field_dict = eval(str(issue_field_dict))
        print issue_field_dict

        new_issue = self.jira.create_issue(issue_field_dict)
        if assign_current_user is True:
            self.assign_user_to_issue(new_issue, self.jira.current_user())
        return new_issue

    def create_issue_link(self, link_type, inwardissue,
                          outwardissue, comment=None):
        """
        Create a link between two issues.

            Arguments:
                |  link_type (string)  	| The type of link									|
                |  inwardissue (string)  	| The issue to link from  							|
                |  outwardissue (string)  	| The issue to link to 								|
                |  comment (string)  		| (Optional) A comment to add when joining issues	|

            Example:
                |  *Keyword*        	|  *Parameters* | 									| 			|
                |  connect to jira      |  asimmons     | options= {'http://devjira01'}     |  			|
                |  ${issue}				|  create issue |  ${issue_field_dict}				|  True 	|
                |  create issue link	|  relates to   |  ${issue} 						|  PROJ-385	|
        """
        self.jira.create_issue_link(type=link_type,
                                    inwardIssue=str(inwardissue),
                                    outwardIssue=str(outwardissue))

    def assign_user_to_issue(self, issue, JIRAUsername):
    # TODO: Review docs
        """
        Adds a user to a specified issue's watcher list

        Arguments:
            |  issue (string)  		| A JIRA Issue that a user needs to be assigned to, can be an issue ID or Key		|
            |  JIRAUsername (string)  	| A JIRA Username to assign a user to an issue   									|

        Example:
           |  *Keyword*        		|  *Parameters* | 									|
           |  connect to jira       |  asimmons     | options= {'http://devjira01'}     |
           |  ${issue}				|  create issue |  ${issue_field_dict}				|
           |  assign user to issue	|  ${issue}		|  aSample 							|
        """
        self.jira.assign_issue(issue=issue, assignee=JIRAUsername)

    def get_current_user(self):
        """
        Returns the current user used in the Connect to JIRA keyword.
        """
        return self.jira.current_user()

    def add_watcher_to_issue(self, issue, JIRAUsername):
        """
        Adds a user to a specified issue's watcher list.

        Arguments:
            |  issue (string)  		| A JIRA Issue that a watcher needs added to, can be an issue ID or Key		|
            |  JIRAUsername (string)  	| A JIRA Username to add as a watcher to an issue   					|

        Example:
            |  *Keyword*        	|  *Parameters* | 								|		|
            |  connect to jira  |  asimmons     | options= {'http://devjira01'}     | 		|
            |  ${issue}				|  create issue |  ${issue_field_dict}			|  True |
            |  add watcher to issue	|  ${issue}		|  aSample 						| 		|

        """
        self.jira.add_watcher(issue=issue, watcher=JIRAUsername)

    def add_comment_to_issue(self, issue, comment, visibility=None):
        """
        Adds a comment to a specified issue from the current user.

            Arguments:
                |  issue (string)  		| A JIRA Issue that a watcher needs added to, can be an issue ID or Key	|
                |  comment (string)  		| A body of text to add as a comment to an issue   						|
                |  visibility (string)  	| (Optional)															|

            Example:
                |  *Keyword*        	|  *Parameters* | 									|		|
                |  connect to jira      |  asimmons     |  options= {'http://devjira01'}    | 		|
                |  ${issue}				|  create issue |  ${issue_field_dict}				|  True |
                |  add comment to issue	|  ${issue}		|  Starting work on this issue		| 		|
        """
        self.jira.add_comment(issue=issue, body=comment)

    def add_attachment_to_issue(self, issue, attachment, filename=None):
        """
        Uploads and attaches a file a specified issue. (Note: include the file extention when using the 'filename' option or this will change the file type.)

        Arguments:
            |   issue (string)  	| A JIRA Issue that a watcher needs added to, can be an issue ID or Key		|
            |   attachment (string) | A string pointing to a file location to upload and attach to the issue	|
            |   filename (string)  	| (Optional) A string to rename the file to upon attaching to the issue  	|

        Example:
            |  *Keyword*        		|  *Parameters* | 							         |						|
            |  connect to jira          |  asimmons     | options= {'http://devjira01'}      | 						|
            |  ${issue}					|  create issue |  ${issue_field_dict}	             |  True 			 	|
            |  add attchment to issue	|  ${issue}		|  ./logfile.text			         |  LogInformation.txt	|
        """
        self.jira.add_attachment(issue=issue, attachment=attachment,
                                 filename=filename)
Esempio n. 37
0
class JiraAlerter(Alerter):
    """ Creates a Jira ticket for each alert """

    required_options = frozenset(["jira_server", "jira_account_file", "jira_project", "jira_issuetype"])

    def __init__(self, rule):
        super(JiraAlerter, self).__init__(rule)
        self.server = self.rule["jira_server"]
        self.get_account(self.rule["jira_account_file"])
        self.project = self.rule["jira_project"]
        self.issue_type = self.rule["jira_issuetype"]
        self.component = self.rule.get("jira_component")
        self.label = self.rule.get("jira_label")
        self.description = self.rule.get("jira_description", "")
        self.assignee = self.rule.get("jira_assignee")
        self.max_age = self.rule.get("jira_max_age", 30)
        self.priority = self.rule.get("jira_priority")
        self.bump_tickets = self.rule.get("jira_bump_tickets", False)
        self.bump_not_in_statuses = self.rule.get("jira_bump_not_in_statuses")
        self.bump_in_statuses = self.rule.get("jira_bump_in_statuses")

        if self.bump_in_statuses and self.bump_not_in_statuses:
            msg = "Both jira_bump_in_statuses (%s) and jira_bump_not_in_statuses (%s) are set." % (
                ",".join(self.bump_in_statuses),
                ",".join(self.bump_not_in_statuses),
            )
            intersection = list(set(self.bump_in_statuses) & set(self.bump_in_statuses))
            if intersection:
                msg = "%s Both have common statuses of (%s). As such, no tickets will ever be found." % (
                    msg,
                    ",".join(intersection),
                )
            msg += " This should be simplified to use only one or the other."
            logging.warning(msg)

        self.jira_args = {"project": {"key": self.project}, "issuetype": {"name": self.issue_type}}

        if self.component:
            self.jira_args["components"] = [{"name": self.component}]
        if self.label:
            self.jira_args["labels"] = [self.label]
        if self.assignee:
            self.jira_args["assignee"] = {"name": self.assignee}

        try:
            self.client = JIRA(self.server, basic_auth=(self.user, self.password))
            self.get_priorities()
        except JIRAError as e:
            # JIRAError may contain HTML, pass along only first 1024 chars
            raise EAException("Error connecting to JIRA: %s" % (str(e)[:1024]))

        try:
            if self.priority is not None:
                self.jira_args["priority"] = {"id": self.priority_ids[self.priority]}
        except KeyError:
            logging.error("Priority %s not found. Valid priorities are %s" % (self.priority, self.priority_ids.keys()))

    def get_priorities(self):
        """ Creates a mapping of priority index to id. """
        priorities = self.client.priorities()
        self.priority_ids = {}
        for x in range(len(priorities)):
            self.priority_ids[x] = priorities[x].id

    def set_assignee(self, assignee):
        self.assignee = assignee
        if assignee:
            self.jira_args["assignee"] = {"name": assignee}
        elif "assignee" in self.jira_args:
            self.jira_args.pop("assignee")

    def find_existing_ticket(self, matches):
        # Default title, get stripped search version
        if "alert_subject" not in self.rule:
            title = self.create_default_title(matches, True)
        else:
            title = self.create_title(matches)

        # This is necessary for search for work. Other special characters and dashes
        # directly adjacent to words appear to be ok
        title = title.replace(" - ", " ")

        date = (datetime.datetime.now() - datetime.timedelta(days=self.max_age)).strftime("%Y/%m/%d")
        jql = 'project=%s AND summary~"%s" and created >= "%s"' % (self.project, title, date)
        if self.bump_in_statuses:
            jql = "%s and status in (%s)" % (jql, ",".join(self.bump_in_statuses))
        if self.bump_not_in_statuses:
            jql = "%s and status not in (%s)" % (jql, ",".join(self.bump_not_in_statuses))
        try:
            issues = self.client.search_issues(jql)
        except JIRAError as e:
            logging.exception("Error while searching for JIRA ticket using jql '%s': %s" % (jql, e))
            return None

        if len(issues):
            return issues[0]

    def comment_on_ticket(self, ticket, match):
        text = unicode(JiraFormattedMatchString(self.rule, match))
        timestamp = pretty_ts(match[self.rule["timestamp_field"]])
        comment = "This alert was triggered again at %s\n%s" % (timestamp, text)
        self.client.add_comment(ticket, comment)

    def alert(self, matches):
        title = self.create_title(matches)

        if self.bump_tickets:
            ticket = self.find_existing_ticket(matches)
            if ticket:
                elastalert_logger.info("Commenting on existing ticket %s" % (ticket.key))
                for match in matches:
                    self.comment_on_ticket(ticket, match)
                if self.pipeline is not None:
                    self.pipeline["jira_ticket"] = ticket
                    self.pipeline["jira_server"] = self.server
                return

        description = self.description + "\n"
        for match in matches:
            description += unicode(JiraFormattedMatchString(self.rule, match))
            if len(matches) > 1:
                description += "\n----------------------------------------\n"

        self.jira_args["summary"] = title
        self.jira_args["description"] = description

        try:
            self.issue = self.client.create_issue(**self.jira_args)
        except JIRAError as e:
            raise EAException("Error creating JIRA ticket: %s" % (e))
        elastalert_logger.info("Opened Jira ticket: %s" % (self.issue))

        if self.pipeline is not None:
            self.pipeline["jira_ticket"] = self.issue
            self.pipeline["jira_server"] = self.server

    def create_default_title(self, matches, for_search=False):
        # If there is a query_key, use that in the title
        if "query_key" in self.rule and self.rule["query_key"] in matches[0]:
            title = "ElastAlert: %s matched %s" % (matches[0][self.rule["query_key"]], self.rule["name"])
        else:
            title = "ElastAlert: %s" % (self.rule["name"])

        if for_search:
            return title

        title += " - %s" % (pretty_ts(matches[0][self.rule["timestamp_field"]], self.rule.get("use_local_time")))

        # Add count for spikes
        count = matches[0].get("spike_count")
        if count:
            title += " - %s+ events" % (count)

        return title

    def get_info(self):
        return {"type": "jira"}
Esempio n. 38
0
class jira_api():

    def __init__(self, server, user, pwd):
        """
        :param server: jira域名
        :param user:jira用户
        :param pwd:jira账户密码
        """
        self.server = server;
        self.basic_auth = (user, pwd)
        self.jiraClient = None

    def login(self):
        self.jiraClient = JIRA(server=self.server, basic_auth=self.basic_auth)
        if self.jiraClient is not None:
            return True
        else:
            return False


    def get_issue_list(self,jql_str=None,page=0,limit=10,fields=None):
        """
        查询issue集合
        :param jql_str: 查询语句
        :param page: 分页参数 开始
        :param limit: 分页参数 结束
        :param fields: 查询字段
        :return:issue: 列表
        """
        if self.jiraClient is None:
            self.login()
        if jql_str is None and jql_str == "":
            return []
        return self.jiraClient.search_issues(jql_str=jql_str, startAt=page, maxResults=limit, fields=fields)

    def get_issue_info(self,id_or_key,fields=None):
        """
        根据id或key查询issue信息
        :param id_or_key: issue id或key
        :param fields: 查询字段
        :return: issue详细信息
        """
        if self.jiraClient is None:
            self.login()
        if id_or_key is None:
            return {}
        return self.jiraClient.issue(id_or_key, fields=fields)

    def get_comments_from_issue(self,issue):
        """
        获取issue中所有comment集合
        :param issue:
        :return:
        """
        if self.jiraClient is None:
            self.login()
        if issue is None:
            return []
        return self.jiraClient.comments(issue)

    def get_comment_from_issue(self,issue,comment_id):
        if self.jiraClient is None:
            self.login()
        if issue is None:
            return {}
        if comment_id is None:
            return {}
        return self.jiraClient.comment(issue, comment_id)

    def add_comment_to_issue(self,issue_id,comment_str):
        if self.jiraClient is None:
            self.login()
        if issue_id is None or issue_id == "":
            return False
        if comment_str is None or comment_str == "":
            return False
        return self.jiraClient.add_comment(issue_id, comment_str)

    def update_comment(self,issue_id_or_key,comment_id,comment_str):
        if self.jiraClient is None:
            self.login()
        if issue_id_or_key is None or issue_id_or_key == "":
            return False
        if comment_id is None or comment_id == "":
            return False
        comment = self.get_comment_from_issue(self.get_issue_info(issue_id_or_key),comment_id)
        if comment is None:
            return False
        return comment.update(body=comment_str)

    def get_transitions_from_issue(self,issue):
        if self.jiraClient is None:
            self.login()
        if issue is None:
            return {}
        return self.jiraClient.transitions(issue)

    def assign_issue_to_user(self,issue_id,assignee):
        if self.jiraClient is None:
            self.login()
        if issue_id is None:
            return False
        if assignee is None:
            return False
        return self.jiraClient.assign_issue(issue_id, assignee)

    def transition_issues(self,issue_id,transition_id,assignee=None,comment=""):
        if self.jiraClient is None:
            self.login()
        if(issue_id is None):
            return False
        if(transition_id is None):
            return False
        if(comment is None):
            return False
        fields = None
        if assignee is not None and assignee != "":
            fields = {'assignee': {'name': assignee}}
        return self.jiraClient.transition_issue(issue=issue_id,fields=fields,transition=transition_id,comment=comment)

    def group_members(self,group_name):
        if self.jiraClient is None:
            self.login()
        if group_name is None:
            return []
        return self.jiraClient.group_members(group_name)

    def get_project_by_key(self,key):
        if self.jiraClient is None:
            self.login()
        if key is None:
            return ""
        return self.jiraClient.project(key)

    def get_custom_field_option(self,id):
        if self.jiraClient is None:
            self.login()
        if id is None:
            return ""
        return self.jiraClient.custom_field_option(id)
import sys, getopt
from jira.client import JIRA
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-t", "--ticketid", help="Enter TicketID. example: OPS-5432")
parser.add_argument("-c", "--comment",help="Please comment")
args = parser.parse_args()



options = {'server':'https://jira.corp.domain.com','verify':False}

jira = JIRA(options, basic_auth=('anto.daniel','xxxxxxxxxxx'))

ticketid = args.ticketid
addcomment = args.comment

comment = jira.add_comment(ticketid, addcomment)
print 'added comment'
Esempio n. 40
0
class JiraAlerter(Alerter):
    """ Creates a Jira ticket for each alert """
    required_options = frozenset(
        ['jira_server', 'jira_account_file', 'jira_project', 'jira_issuetype'])

    def __init__(self, rule):
        super(JiraAlerter, self).__init__(rule)
        self.server = self.rule['jira_server']
        self.get_account(self.rule['jira_account_file'])
        self.project = self.rule['jira_project']
        self.issue_type = self.rule['jira_issuetype']
        self.component = self.rule.get('jira_component')
        self.label = self.rule.get('jira_label')
        self.assignee = self.rule.get('jira_assignee')
        self.max_age = self.rule.get('jira_max_age', 30)
        self.priority = self.rule.get('jira_priority')
        self.bump_tickets = self.rule.get('jira_bump_tickets', False)
        self.bump_not_in_statuses = self.rule.get('jira_bump_not_in_statuses')
        self.bump_in_statuses = self.rule.get('jira_bump_in_statuses')
        if self.bump_in_statuses and self.bump_not_in_statuses:
            msg = 'Both jira_bump_in_statuses (%s) and jira_bump_not_in_statuses (%s) are set.' % \
                  (','.join(self.bump_in_statuses), ','.join(self.bump_not_in_statuses))
            intersection = list(
                set(self.bump_in_statuses) & set(self.bump_in_statuses))
            if intersection:
                msg = '%s Both have common statuses of (%s). As such, no tickets will ever be found.' % (
                    msg, ','.join(intersection))
            msg += ' This should be simplified to use only one or the other.'
            logging.warning(msg)

        self.jira_args = {
            'project': {
                'key': self.project
            },
            'issuetype': {
                'name': self.issue_type
            }
        }

        if self.component:
            self.jira_args['components'] = [{'name': self.component}]
        if self.label:
            self.jira_args['labels'] = [self.label]
        if self.assignee:
            self.jira_args['assignee'] = {'name': self.assignee}

        try:
            self.client = JIRA(self.server,
                               basic_auth=(self.user, self.password))
            self.get_priorities()
        except JIRAError as e:
            # JIRAError may contain HTML, pass along only first 1024 chars
            raise EAException("Error connecting to JIRA: %s" % (str(e)[:1024]))

        try:
            if self.priority is not None:
                self.jira_args['priority'] = {
                    'id': self.priority_ids[self.priority]
                }
        except KeyError:
            logging.error("Priority %s not found. Valid priorities are %s" %
                          (self.priority, self.priority_ids.keys()))

    def get_priorities(self):
        """ Creates a mapping of priority index to id. """
        priorities = self.client.priorities()
        self.priority_ids = {}
        for x in range(len(priorities)):
            self.priority_ids[x] = priorities[x].id

    def set_assignee(self, assignee):
        self.assignee = assignee
        if assignee:
            self.jira_args['assignee'] = {'name': assignee}
        elif 'assignee' in self.jira_args:
            self.jira_args.pop('assignee')

    def find_existing_ticket(self, matches):
        # Default title, get stripped search version
        if 'alert_subject' not in self.rule:
            title = self.create_default_title(matches, True)
        else:
            title = self.create_title(matches)

        # This is necessary for search for work. Other special characters and dashes
        # directly adjacent to words appear to be ok
        title = title.replace(' - ', ' ')

        date = (datetime.datetime.now() -
                datetime.timedelta(days=self.max_age)).strftime('%Y/%m/%d')
        jql = 'project=%s AND summary~"%s" and created >= "%s"' % (
            self.project, title, date)
        if self.bump_in_statuses:
            jql = '%s and status in (%s)' % (jql, ','.join(
                self.bump_in_statuses))
        if self.bump_not_in_statuses:
            jql = '%s and status not in (%s)' % (jql, ','.join(
                self.bump_not_in_statuses))
        try:
            issues = self.client.search_issues(jql)
        except JIRAError as e:
            logging.exception(
                "Error while searching for JIRA ticket using jql '%s': %s" %
                (jql, e))
            return None

        if len(issues):
            return issues[0]

    def comment_on_ticket(self, ticket, match):
        text = str(JiraFormattedMatchString(self.rule, match))
        timestamp = pretty_ts(match[self.rule['timestamp_field']])
        comment = "This alert was triggered again at %s\n%s" % (timestamp,
                                                                text)
        self.client.add_comment(ticket, comment)

    def alert(self, matches):
        title = self.create_title(matches)

        if self.bump_tickets:
            ticket = self.find_existing_ticket(matches)
            if ticket:
                logging.info('Commenting on existing ticket %s' % (ticket.key))
                for match in matches:
                    self.comment_on_ticket(ticket, match)
                if self.pipeline is not None:
                    self.pipeline['jira_ticket'] = ticket
                return

        description = ''
        for match in matches:
            description += str(JiraFormattedMatchString(self.rule, match))
            if len(matches) > 1:
                description += '\n----------------------------------------\n'

        self.jira_args['summary'] = title
        self.jira_args['description'] = description

        try:
            self.issue = self.client.create_issue(**self.jira_args)
        except JIRAError as e:
            raise EAException("Error creating JIRA ticket: %s" % (e))
        logging.info("Opened Jira ticket: %s" % (self.issue))

        if self.pipeline is not None:
            self.pipeline['jira_ticket'] = self.issue

    def create_default_title(self, matches, for_search=False):
        # If there is a query_key, use that in the title
        if 'query_key' in self.rule and self.rule['query_key'] in matches[0]:
            title = 'ElastAlert: %s matched %s' % (
                matches[0][self.rule['query_key']], self.rule['name'])
        else:
            title = 'ElastAlert: %s' % (self.rule['name'])

        if for_search:
            return title

        title += ' - %s' % (pretty_ts(matches[0][self.rule['timestamp_field']],
                                      self.rule.get('use_local_time')))

        # Add count for spikes
        count = matches[0].get('spike_count')
        if count:
            title += ' - %s+ events' % (count)

        return title

    def get_info(self):
        return {'type': 'jira'}
Esempio n. 41
0
# Sort available project keys, then return the second, third, and fourth keys.
keys = sorted([project.key for project in projects])[2:5]

# Get an issue.
issue = jira.issue('JRA-1330')

# Find all comments made by Atlassians on this issue.
import re
atl_comments = [
    comment for comment in issue.fields.comment.comments
    if re.search(r'@atlassian.com$', comment.author.emailAddress)
]

# Add a comment to the issue.
jira.add_comment(issue, 'Comment text')

# Change the issue's summary and description.
issue.update(summary="I'm different!",
             description='Changed the summary to be different.')

# You can update the entire labels field like this
issue.update(labels=['AAA', 'BBB'])

# Or modify the List of existing labels. The new label is unicode with no spaces
issue.fields.labels.append(u'new_text')
issue.update(fields={"labels": issue.fields.labels})

# Send the issue away for good.
issue.delete()
Esempio n. 42
0
class JiraAlerter(Alerter):
    """ Creates a Jira ticket for each alert """
    required_options = frozenset(
        ['jira_server', 'jira_account_file', 'jira_project', 'jira_issuetype'])

    def __init__(self, rule):
        super(JiraAlerter, self).__init__(rule)
        self.server = self.rule['jira_server']
        self.get_jira_account(self.rule['jira_account_file'])
        self.project = self.rule['jira_project']
        self.issue_type = self.rule['jira_issuetype']
        self.component = self.rule.get('jira_component')
        self.label = self.rule.get('jira_label')
        self.assignee = self.rule.get('jira_assignee')
        self.max_age = self.rule.get('jira_max_age', 30)
        self.bump_tickets = self.rule.get('jira_bump_tickets', False)

        self.jira_args = {
            'project': {
                'key': self.project
            },
            'issuetype': {
                'name': self.issue_type
            }
        }

        if self.component:
            self.jira_args['components'] = [{'name': self.component}]
        if self.label:
            self.jira_args['labels'] = [self.label]
        if self.assignee:
            self.jira_args['assignee'] = {'name': self.assignee}

        try:
            self.client = JIRA(self.server,
                               basic_auth=(self.user, self.password))
        except JIRAError as e:
            # JIRAError may contain HTML, pass along only first 1024 chars
            raise EAException("Error connecting to JIRA: %s" % (str(e)[:1024]))

    def set_assignee(self, assignee):
        self.assignee = assignee
        if assignee:
            self.jira_args['assignee'] = {'name': assignee}
        elif 'assignee' in self.jira_args:
            self.jira_args.pop('assignee')

    def get_jira_account(self, account_file):
        """ Gets the username and password from a jira account file.

        :param account_file: Name of the file which contains user and password information.
        """
        account_conf = yaml_loader(account_file)
        if 'user' not in account_conf or 'password' not in account_conf:
            raise EAException(
                'Jira account file must have user and password fields')
        self.user = account_conf['user']
        self.password = account_conf['password']

    def find_existing_ticket(self, matches):
        # Default title, get stripped search version
        if 'alert_subject' not in self.rule:
            title = self.create_default_title(matches, True)
        else:
            title = self.create_title(matches)

        # This is necessary for search for work. Other special characters and dashes
        # directly adjacent to words appear to be ok
        title = title.replace(' - ', ' ')

        date = (datetime.datetime.now() -
                datetime.timedelta(days=self.max_age)).strftime('%Y/%m/%d')
        jql = 'project=%s AND summary~"%s" and created >= "%s"' % (
            self.project, title, date)
        issues = self.client.search_issues(jql)
        if len(issues):
            return issues[0]

    def comment_on_ticket(self, ticket, match):
        text = basic_match_string(self.rule, match)
        timestamp = pretty_ts(match[self.rule['timestamp_field']])
        comment = "This alert was triggered again at %s\n%s" % (timestamp,
                                                                text)
        self.client.add_comment(ticket, comment)

    def alert(self, matches):
        title = self.create_title(matches)

        if self.bump_tickets:
            ticket = self.find_existing_ticket(matches)
            if ticket:
                logging.info('Commenting on existing ticket %s' % (ticket.key))
                for match in matches:
                    self.comment_on_ticket(ticket, match)
                return

        description = ''
        for match in matches:
            description += basic_match_string(self.rule, match)
            if len(matches) > 1:
                description += '\n----------------------------------------\n'

        self.jira_args['summary'] = title
        self.jira_args['description'] = description

        try:
            self.issue = self.client.create_issue(**self.jira_args)
        except JIRAError as e:
            raise EAException("Error creating JIRA ticket: %s" % (e))
        logging.info("Opened Jira ticket: %s" % (self.issue))

    def create_default_title(self, matches, for_search=False):
        # If there is a query_key, use that in the title
        if 'query_key' in self.rule and self.rule['query_key'] in matches[0]:
            title = 'ElastAlert: %s matched %s' % (
                matches[0][self.rule['query_key']], self.rule['name'])
        else:
            title = 'ElastAlert: %s' % (self.rule['name'])

        if for_search:
            return title

        title += ' - %s' % (pretty_ts(matches[0][self.rule['timestamp_field']],
                                      self.rule.get('use_local_time')))

        # Add count for spikes
        count = matches[0].get('spike_count')
        if count:
            title += ' - %s+ events' % (count)

        return title

    def get_info(self):
        return {'type': 'jira'}
Esempio n. 43
0
class JiraAlerter(Alerter):
    """ Creates a Jira ticket for each alert """
    required_options = frozenset(['jira_server', 'jira_account_file', 'jira_project', 'jira_issuetype'])

    # Maintain a static set of built-in fields that we explicitly know how to set
    # For anything else, we will do best-effort and try to set a string value
    known_field_list = [
        'jira_account_file',
        'jira_assignee',
        'jira_bump_in_statuses',
        'jira_bump_not_in_statuses',
        'jira_bump_tickets',
        'jira_component',
        'jira_components',
        'jira_description',
        'jira_ignore_in_title',
        'jira_issuetype',
        'jira_label',
        'jira_labels',
        'jira_max_age',
        'jira_priority',
        'jira_project',
        'jira_server',
        'jira_watchers',
    ]

    # Some built-in jira types that can be used as custom fields require special handling
    # Here is a sample of one of them:
    # {"id":"customfield_12807","name":"My Custom Field","custom":true,"orderable":true,"navigable":true,"searchable":true,
    # "clauseNames":["cf[12807]","My Custom Field"],"schema":{"type":"array","items":"string",
    # "custom":"com.atlassian.jira.plugin.system.customfieldtypes:multiselect","customId":12807}}
    # There are likely others that will need to be updated on a case-by-case basis
    custom_string_types_with_special_handling = [
        'com.atlassian.jira.plugin.system.customfieldtypes:multicheckboxes',
        'com.atlassian.jira.plugin.system.customfieldtypes:multiselect',
        'com.atlassian.jira.plugin.system.customfieldtypes:radiobuttons',
    ]

    def __init__(self, rule):
        super(JiraAlerter, self).__init__(rule)
        self.server = self.rule['jira_server']
        self.get_account(self.rule['jira_account_file'])
        self.project = self.rule['jira_project']
        self.issue_type = self.rule['jira_issuetype']

        # We used to support only a single component. This allows us to maintain backwards compatibility
        # while also giving the user-facing API a more representative name
        self.components = self.rule.get('jira_components', self.rule.get('jira_component'))

        # We used to support only a single label. This allows us to maintain backwards compatibility
        # while also giving the user-facing API a more representative name
        self.labels = self.rule.get('jira_labels', self.rule.get('jira_label'))

        self.description = self.rule.get('jira_description', '')
        self.assignee = self.rule.get('jira_assignee')
        self.max_age = self.rule.get('jira_max_age', 30)
        self.priority = self.rule.get('jira_priority')
        self.bump_tickets = self.rule.get('jira_bump_tickets', False)
        self.bump_not_in_statuses = self.rule.get('jira_bump_not_in_statuses')
        self.bump_in_statuses = self.rule.get('jira_bump_in_statuses')
        self.watchers = self.rule.get('jira_watchers')

        if self.bump_in_statuses and self.bump_not_in_statuses:
            msg = 'Both jira_bump_in_statuses (%s) and jira_bump_not_in_statuses (%s) are set.' % \
                  (','.join(self.bump_in_statuses), ','.join(self.bump_not_in_statuses))
            intersection = list(set(self.bump_in_statuses) & set(self.bump_in_statuses))
            if intersection:
                msg = '%s Both have common statuses of (%s). As such, no tickets will ever be found.' % (
                    msg, ','.join(intersection))
            msg += ' This should be simplified to use only one or the other.'
            logging.warning(msg)

        self.jira_args = {'project': {'key': self.project},
                          'issuetype': {'name': self.issue_type}}

        if self.components:
            # Support single component or list
            if type(self.components) != list:
                self.jira_args['components'] = [{'name': self.components}]
            else:
                self.jira_args['components'] = [{'name': component} for component in self.components]
        if self.labels:
            # Support single label or list
            if type(self.labels) != list:
                self.labels = [self.labels]
            self.jira_args['labels'] = self.labels
        if self.watchers:
            # Support single watcher or list
            if type(self.watchers) != list:
                self.watchers = [self.watchers]
        if self.assignee:
            self.jira_args['assignee'] = {'name': self.assignee}

        try:
            self.client = JIRA(self.server, basic_auth=(self.user, self.password))
            self.get_priorities()
            self.get_arbitrary_fields()
        except JIRAError as e:
            # JIRAError may contain HTML, pass along only first 1024 chars
            raise EAException("Error connecting to JIRA: %s" % (str(e)[:1024]))

        try:
            if self.priority is not None:
                self.jira_args['priority'] = {'id': self.priority_ids[self.priority]}
        except KeyError:
            logging.error("Priority %s not found. Valid priorities are %s" % (self.priority, self.priority_ids.keys()))

    def get_arbitrary_fields(self):
        # This API returns metadata about all the fields defined on the jira server (built-ins and custom ones)
        fields = self.client.fields()
        for jira_field, value in self.rule.iteritems():
            # If we find a field that is not covered by the set that we are aware of, it means it is either:
            # 1. A built-in supported field in JIRA that we don't have on our radar
            # 2. A custom field that a JIRA admin has configured
            if jira_field.startswith('jira_') and jira_field not in self.known_field_list:
                # Remove the jira_ part.  Convert underscores to spaces
                normalized_jira_field = jira_field[5:].replace('_', ' ').lower()
                # All jira fields should be found in the 'id' or the 'name' field. Therefore, try both just in case
                for identifier in ['name', 'id']:
                    field = next((f for f in fields if normalized_jira_field == f[identifier].replace('_', ' ').lower()), None)
                    if field:
                        break
                if not field:
                    # Log a warning to ElastAlert saying that we couldn't find that type?
                    # OR raise and fail to load the alert entirely? Probably the latter...
                    raise Exception("Could not find a definition for the jira field '{0}'".format(normalized_jira_field))
                arg_name = field['id']
                # Check the schema information to decide how to set the value correctly
                # If the schema information is not available, raise an exception since we don't know how to set it
                # Note this is only the case for two built-in types, id: issuekey and id: thumbnail
                if not ('schema' in field or 'type' in field['schema']):
                    raise Exception("Could not determine schema information for the jira field '{0}'".format(normalized_jira_field))
                arg_type = field['schema']['type']

                # Handle arrays of simple types like strings or numbers
                if arg_type == 'array':
                    # As a convenience, support the scenario wherein the user only provides
                    # a single value for a multi-value field e.g. jira_labels: Only_One_Label
                    if type(value) != list:
                        value = [value]
                    array_items = field['schema']['items']
                    # Simple string types
                    if array_items in ['string', 'date', 'datetime']:
                        # Special case for multi-select custom types (the JIRA metadata says that these are strings, but
                        # in reality, they are required to be provided as an object.
                        if 'custom' in field['schema'] and field['schema']['custom'] in self.custom_string_types_with_special_handling:
                            self.jira_args[arg_name] = [{'value': v} for v in value]
                        else:
                            self.jira_args[arg_name] = value
                    elif array_items == 'number':
                        self.jira_args[arg_name] = [int(v) for v in value]
                    # Also attempt to handle arrays of complex types that have to be passed as objects with an identifier 'key'
                    else:
                        # Try setting it as an object, using 'name' as the key
                        # This may not work, as the key might actually be 'key', 'id', 'value', or something else
                        # If it works, great!  If not, it will manifest itself as an API error that will bubble up
                        self.jira_args[arg_name] = [{'name': v} for v in value]
                # Handle non-array types
                else:
                    # Simple string types
                    if arg_type in ['string', 'date', 'datetime']:
                        # Special case for custom types (the JIRA metadata says that these are strings, but
                        # in reality, they are required to be provided as an object.
                        if 'custom' in field['schema'] and field['schema']['custom'] in self.custom_string_types_with_special_handling:
                            self.jira_args[arg_name] = {'value': value}
                        else:
                            self.jira_args[arg_name] = value
                    # Number type
                    elif arg_type == 'number':
                        self.jira_args[arg_name] = int(value)
                    # Complex type
                    else:
                        self.jira_args[arg_name] = {'name': value}

    def get_priorities(self):
        """ Creates a mapping of priority index to id. """
        priorities = self.client.priorities()
        self.priority_ids = {}
        for x in range(len(priorities)):
            self.priority_ids[x] = priorities[x].id

    def set_assignee(self, assignee):
        self.assignee = assignee
        if assignee:
            self.jira_args['assignee'] = {'name': assignee}
        elif 'assignee' in self.jira_args:
            self.jira_args.pop('assignee')

    def find_existing_ticket(self, matches):
        # Default title, get stripped search version
        if 'alert_subject' not in self.rule:
            title = self.create_default_title(matches, True)
        else:
            title = self.create_title(matches)

        if 'jira_ignore_in_title' in self.rule:
            title = title.replace(matches[0].get(self.rule['jira_ignore_in_title'], ''), '')

        # This is necessary for search to work. Other special characters and dashes
        # directly adjacent to words appear to be ok
        title = title.replace(' - ', ' ')
        title = title.replace('\\', '\\\\')

        date = (datetime.datetime.now() - datetime.timedelta(days=self.max_age)).strftime('%Y-%m-%d')
        jql = 'project=%s AND summary~"%s" and created >= "%s"' % (self.project, title, date)
        if self.bump_in_statuses:
            jql = '%s and status in (%s)' % (jql, ','.join(self.bump_in_statuses))
        if self.bump_not_in_statuses:
            jql = '%s and status not in (%s)' % (jql, ','.join(self.bump_not_in_statuses))
        try:
            issues = self.client.search_issues(jql)
        except JIRAError as e:
            logging.exception("Error while searching for JIRA ticket using jql '%s': %s" % (jql, e))
            return None

        if len(issues):
            return issues[0]

    def comment_on_ticket(self, ticket, match):
        text = unicode(JiraFormattedMatchString(self.rule, match))
        timestamp = pretty_ts(lookup_es_key(match, self.rule['timestamp_field']))
        comment = "This alert was triggered again at %s\n%s" % (timestamp, text)
        self.client.add_comment(ticket, comment)

    def alert(self, matches):
        title = self.create_title(matches)

        if self.bump_tickets:
            ticket = self.find_existing_ticket(matches)
            if ticket:
                elastalert_logger.info('Commenting on existing ticket %s' % (ticket.key))
                for match in matches:
                    try:
                        self.comment_on_ticket(ticket, match)
                    except JIRAError as e:
                        logging.exception("Error while commenting on ticket %s: %s" % (ticket, e))
                if self.pipeline is not None:
                    self.pipeline['jira_ticket'] = ticket
                    self.pipeline['jira_server'] = self.server
                return None

        self.jira_args['summary'] = title
        self.jira_args['description'] = self.create_alert_body(matches)

        try:
            self.issue = self.client.create_issue(**self.jira_args)

            # You can not add watchers on initial creation. Only as a follow-up action
            if self.watchers:
                for watcher in self.watchers:
                    try:
                        self.client.add_watcher(self.issue.key, watcher)
                    except Exception as ex:
                        # Re-raise the exception, preserve the stack-trace, and give some
                        # context as to which watcher failed to be added
                        raise Exception("Exception encountered when trying to add '{0}' as a watcher. Does the user exist?\n{1}" .format(watcher, ex)), None, sys.exc_info()[2]

        except JIRAError as e:
            raise EAException("Error creating JIRA ticket: %s" % (e))
        elastalert_logger.info("Opened Jira ticket: %s" % (self.issue))

        if self.pipeline is not None:
            self.pipeline['jira_ticket'] = self.issue
            self.pipeline['jira_server'] = self.server

    def create_alert_body(self, matches):
        body = self.description + '\n'
        body += self.get_aggregation_summary_text(matches)
        for match in matches:
            body += unicode(JiraFormattedMatchString(self.rule, match))
            if len(matches) > 1:
                body += '\n----------------------------------------\n'
        return body

    def get_aggregation_summary_text(self, matches):
        text = super(JiraAlerter, self).get_aggregation_summary_text(matches)
        if text:
            text = u'{{noformat}}{0}{{noformat}}'.format(text)
        return text

    def create_default_title(self, matches, for_search=False):
        # If there is a query_key, use that in the title
        if 'query_key' in self.rule and self.rule['query_key'] in matches[0]:
            title = 'ElastAlert: %s matched %s' % (matches[0][self.rule['query_key']], self.rule['name'])
        else:
            title = 'ElastAlert: %s' % (self.rule['name'])

        if for_search:
            return title

        title += ' - %s' % (pretty_ts(matches[0][self.rule['timestamp_field']], self.rule.get('use_local_time')))

        # Add count for spikes
        count = matches[0].get('spike_count')
        if count:
            title += ' - %s+ events' % (count)

        return title

    def get_info(self):
        return {'type': 'jira'}
Esempio n. 44
0
class JiraExceptionReporterMiddleware:
    
    def __init__(self):
        
        # If we're in debug mode, and JIRA_REPORT_IN_DEBUG is false (or not set)
        # then don't report errors
        if settings.DEBUG:
            try:
                if not settings.JIRA_REPORT_IN_DEBUG:
                    raise MiddlewareNotUsed
            except AttributeError:
                raise MiddlewareNotUsed
        
        # Silently fail if any settings are missing
        try:
            settings.JIRA_ISSUE_DEFAULTS
            settings.JIRA_REOPEN_CLOSED
            settings.JIRA_WONT_FIX

            auth = getattr(settings, "JIRA_AUTH_TYPE", "basic").lower()
            if auth not in ("basic","oauth"):
                auth = "basic"
            
            # Set up JIRA Client
            if auth == "basic":
                self._jira = JIRA(basic_auth=(settings.JIRA_USER,
                    settings.JIRA_PASSWORD), options={"server":
                    settings.JIRA_URL})
        
        except AttributeError:
            raise MiddlewareNotUsed
    
    def process_exception(self, request, exc):
        try:
            # Don't log 404 errors
            if isinstance(exc, Http404):
                return
            
            # This parses the traceback - so we can get the name of the function
            # which generated this exception
            exc_tb = traceback.extract_tb(sys.exc_info()[2])
            
            # Build our issue title in the form "ExceptionType thrown by function name"
            issue_title = re.sub(r'"', r'\\\"', type(exc).__name__ + ' thrown by ' + exc_tb[-1][2])
            issue_message = repr(exc.message) + '\n\n' + \
                            '{noformat:title=Traceback}\n' + traceback.format_exc() + '\n{noformat}\n\n' + \
                            '{noformat:title=Request}\n' + repr(request) + '\n{noformat}'
            
            # See if this exception has already been reported inside JIRA
            try:
                existing = self._jira.search_issues('project = "' +
                        settings.JIRA_ISSUE_DEFAULTS['project']["key"] + '" AND summary ~ "' + issue_title + '"', maxResults=1)
            except JIRAError as e:
                raise
            
            # If it has, add a comment noting that we've had another report of it
            found = False
            for issue in existing:
                if issue_title == issue.fields.summary:
                
                    # If this issue is closed, reopen it
                    if int(issue.fields.status.id) in settings.JIRA_REOPEN_CLOSED \
                            and (issue.fields.resolution and int(issue.fields.resolution.id) != settings.JIRA_WONT_FIX):
                        self._jira.transition_issue(issue,
                                str(settings.JIRA_REOPEN_ACTION))

                        reopened = True
                    else:
                        reopened = False
                    
                    # Add a comment
                    if reopened or not getattr(settings, 'JIRA_COMMENT_REOPEN_ONLY', False):
                        self._jira.add_comment(issue, issue_message)
                    
                    found = True
                    break
            
            if not found:
                # Otherwise, create it
                issue = settings.JIRA_ISSUE_DEFAULTS.copy()
                issue['summary'] = issue_title
                issue['description'] = issue_message
            
                self._jira.create_issue(fields=issue)
        except:
            raise
        else:
            settings.ADMINS = ()