def create_jira(self, username): options = { 'server': '%s' % settings.JIRA_OPTS['URL'], 'verify': settings.JIRA_OPTS.get('VERIFY_SSL', False) } project = self.cleaned_data.get('project') summary = self.cleaned_data.get('summary') issue_type = self.cleaned_data.get('issue_type') component = self.cleaned_data.get('component') description = self.cleaned_data.get('description') description += '*JIRA Created by:* %s' % username try: jconn = JIRA(options, basic_auth=(settings.JIRA_OPTS['USER'], settings.JIRA_OPTS['PASSWORD'])) except Exception as e: logger.error('Error creating JIRA ticket :%s' % e) raise forms.ValidationError(u"Error connecting to JIRA ticket, please check the server logs") try: jira_ticket = jconn.create_issue(project=project, summary=summary, description=description, issuetype={'name': issue_type}, components=[{'name': component}]) except Exception as e: logger.error('Error creating JIRA ticket project=%s, summary=%s, issue_type=%s, description=%s,' % (project, summary, issue_type, description)) logger.error('Server message %s' % e) msg = u"Error creating JIRA ticket, please check the project name and issue type. If that doesn't work then check the server logs" raise forms.ValidationError(msg) if isinstance(jira_ticket, jira.resources.Issue): return jira_ticket.key else: raise forms.ValidationError(u"Error creating JIRA ticket, JIRA server did not return a ticket key.")
def add_issue(find, push_to_jira): eng = Engagement.objects.get(test=find.test) prod = Product.objects.get(engagement= eng) jpkey = JIRA_PKey.objects.get(product=prod) jira_conf = jpkey.conf if push_to_jira: if 'Active' in find.status() and 'Verified' in find.status(): try: JIRAError.log_to_tempfile=False jira = JIRA(server=jira_conf.url, basic_auth=(jira_conf.username, jira_conf.password)) if jpkey.component: new_issue = jira.create_issue(project=jpkey.project_key, summary=find.title, components=[{'name': jpkey.component}, ], description=jira_long_description(find.long_desc(), find.id, jira_conf.finding_text), issuetype={'name': jira_conf.default_issue_type}, priority={'name': jira_conf.get_priority(find.severity)}) else: new_issue = jira.create_issue(project=jpkey.project_key, summary=find.title, description=jira_long_description(find.long_desc(), find.id, jira_conf.finding_text), issuetype={'name': jira_conf.default_issue_type}, priority={'name': jira_conf.get_priority(find.severity)}) j_issue = JIRA_Issue(jira_id=new_issue.id, jira_key=new_issue, finding=find) j_issue.save() issue = jira.issue(new_issue.id) #Add labels (security & product) add_labels(find, new_issue) #Upload dojo finding screenshots to Jira for pic in find.images.all(): jira_attachment(jira, issue, settings.MEDIA_ROOT + pic.image_large.name) #if jpkey.enable_engagement_epic_mapping: # epic = JIRA_Issue.objects.get(engagement=eng) # issue_list = [j_issue.jira_id,] # jira.add_issues_to_epic(epic_id=epic.jira_id, issue_keys=[str(j_issue.jira_id)], ignore_epics=True) except JIRAError as e: log_jira_alert(e.text, find) else: log_jira_alert("Finding not active or verified.", find)
class JiraApi(): def __init__(self, instance=None): self.instance = instance @staticmethod def get_datetime_now(): return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.0+0000") def connect(self): options = {'server': app.config['JIRA_HOSTNAME'],'verify':False} self.instance = JIRA(options, basic_auth=(app.config['JIRA_USERNAME'], app.config['JIRA_PASSWORD'])) @staticmethod def ticket_link(issue): return '<a href="{}/browse/{}">{}</a>'.format(app.config['JIRA_HOSTNAME'], issue.key, issue.key) def resolve(self, issue): self.instance.transition_issue( issue, app.config['JIRA_RESOLVE_TRANSITION_ID'], assignee={'name': app.config['JIRA_USERNAME']}, resolution={'id': app.config['JIRA_RESOLVE_STATE_ID']}) def defect_for_exception(self, summary_title, e): return self.instance.create_issue( project='IPGBD', summary='[auto-{}] Problem: {}'.format(current_user.username, summary_title), description="Exception: {}".format(e), customfield_13842=JiraApi.get_datetime_now(), customfield_13838= { "self": "https://jira.rim.net/rest/api/2/customFieldOption/16680", "value": "No", "id": "16680" }, customfield_13831 = [ { "self": "https://jira.rim.net/rest/api/2/customFieldOption/16592", "value": "Quality", "id": "16592" }, { "self": "https://jira.rim.net/rest/api/2/customFieldOption/16594", "value": "Risk Avoidance", "id": "16594" }], issuetype={'name': 'Defect'})
def add_epic(eng, push_to_jira): engagement = eng prod = Product.objects.get(engagement=engagement) jpkey = JIRA_PKey.objects.get(product=prod) jira_conf = jpkey.conf if jpkey.enable_engagement_epic_mapping and push_to_jira: issue_dict = { 'project': {'key': jpkey.project_key}, 'summary': engagement.name, 'description' : engagement.name, 'issuetype': {'name': 'Epic'}, 'customfield_' + str(jira_conf.epic_name_id) : engagement.name, } jira = JIRA(server=jira_conf.url, basic_auth=(jira_conf.username, jira_conf.password)) new_issue = jira.create_issue(fields=issue_dict) j_issue = JIRA_Issue(jira_id=new_issue.id, jira_key=new_issue, engagement=engagement) j_issue.save()
def create_issue(self, blank_issue=False): self.__populate_fields() if blank_issue: self.DESCRIPTION_TEMPLATE = "" jira = JIRA(options=self.JIRA_OPTIONS, basic_auth=(self.USER_NAME, self.USER_PW)) # jira-python API new_issue = jira.create_issue(fields=self.ISSUE_FIELDS) issue_url = self.JIRA_SERVER + "browse/" + new_issue.key wb.open_new_tab(issue_url) print '[+] Created a new ticket: ', self.SUMMARY_TEMPLATE print '[+] Issue:', new_issue.key print '---------------------------------------------------' print ' +++++++++++++++++++++++++++++++++++++++++++++++++ ' print '---------------------------------------------------' print '[+] Template used:' print self.DESCRIPTION_TEMPLATE
def __init__(self): options = { 'server': 'http://192.168.33.49:8080' } jira_client = JIRA(options, basic_auth=('scm', 'scm')) # 创建项目模块 # print jira_client.create_component(name=u'项目共性问题', project='SCM') # 创建issue issue_dict = { 'project': {'key': 'PUBISSUE'}, 'summary': u'项目共性问题测试002', 'issuetype': {'name': 'New Feature'}, } new_issue = jira_client.create_issue(fields=issue_dict)
def import_issue(self, message, bug): """import lp ____: Creates a Jira ticket based on ___ bug""" jira = JIRA( basic_auth=(settings.JIRA_USER, settings.JIRA_PASS), server=settings.JIRA_HOST, validate=False, options={'verify': False} ) issues = jira.search_issues(self.SEARCH_PATTERN % ( settings.JIRA_PROJ, bug, bug)) if len(issues) > 0: self.reply( message, self.render_jira(issues[0]), html=True ) else: launchpad = Launchpad.login_anonymously( 'will_jira_import', 'production' ) try: launchpad.bugs[bug] except KeyError: self.reply(message, "That issue does not exist in Launchpad") return issue_dict = { 'project': {'key': settings.JIRA_PROJ}, 'summary': launchpad.bugs[bug].title, 'description': launchpad.bugs[bug].description, 'issuetype': {'name': 'Bug'}, 'customfield_10602': launchpad.bugs[bug].web_link, 'components': [{'id': '18567'}], 'reporter': {'name': self.get_requester_email(message)} } self.reply(message, "I need to create that") new_issue = jira.create_issue(fields=issue_dict) self.reply(message, self.render_jira(new_issue), html=True)
def create_project(params): api_server = params['api_url'] api_username = params['api_username'] api_password = params['api_password'] auth = (api_username, api_password) jira = JIRA( {'server': api_server}, basic_auth=auth) project_key = params['project_key'] project_name = params['project_name'] assignee = params['assignee'] # create project and view if not jira.create_project(project_key, name=project_name, assignee=assignee): return None proj = jira.project(project_key) board = jira.create_board("{0} View".format(project_name), project_key, preset='scrum') issue = jira.create_issue( project=project_key, summary="Sample Issue for {0}".format(project_name), description="This is just sample", issuetype={"name":"Task"} ) return proj
def create_jira(self, username): options = {"server": "%s" % settings.JIRA_OPTS["URL"], "verify": settings.JIRA_OPTS.get("VERIFY_SSL", False)} project = self.cleaned_data.get("project") summary = self.cleaned_data.get("summary") issue_type = self.cleaned_data.get("issue_type") component = self.cleaned_data.get("component") description = self.cleaned_data.get("description") description += "*JIRA Created by:* %s" % username try: jconn = JIRA(options, basic_auth=(settings.JIRA_OPTS["USER"], settings.JIRA_OPTS["PASSWORD"])) except Exception as e: logger.error("Error creating JIRA ticket :%s" % e) raise forms.ValidationError("Error connecting to JIRA ticket, please check the server logs") try: jira_ticket = jconn.create_issue( project=project, summary=summary, description=description, issuetype={"name": issue_type}, components=[{"name": component}], ) except Exception as e: logger.error( "Error creating JIRA ticket project=%s, summary=%s, issue_type=%s, description=%s," % (project, summary, issue_type, description) ) logger.error("Server message %s" % e) msg = "Error creating JIRA ticket, please check the project name and issue type. If that doesn't work then check the server logs" raise forms.ValidationError(msg) if isinstance(jira_ticket, jira.resources.Issue): return jira_ticket.key else: raise forms.ValidationError("Error creating JIRA ticket, JIRA server did not return a ticket key.")
# example # def getAllUsers(): # for user in jira.group_members("API"): # print(jira.user(user)) # for group in jira.groups(): # print(group) if __name__ == "__main__": (credentials, ticketInfo) = loadTicketInfo() jira = JIRA( options={"server": credentials["server"]}, basic_auth=(credentials["username"], credentials["password"]), ) for assignee in ticketInfo["assignees"]: newIssue = jira.create_issue( project=ticketInfo["project"], summary=ticketInfo["summary"], description=ticketInfo["description"], issuetype={"name": ticketInfo["issueType"]}, assignee={"accountId": assignee["id"]}, ) for attachmentName in ticketInfo["attachmentNames"]: with open(attachmentName, "rb") as f: jira.add_attachment(newIssue, f) print("Created ticket %s and assigned it to %s" % (newIssue, assignee["name"]))
def submit_jira_ticket(request): jira_setting = jirasetting.objects.all() for jira in jira_setting: jira_url = jira.jira_server username = jira.jira_username password = jira.jira_password jira_server = jira_url jira_username = signing.loads(username) jira_password = signing.loads(password) options = {'server': jira_server} jira_ser = JIRA(options, basic_auth=(jira_username, jira_password)) jira_projects = jira_ser.projects() if request.method == 'GET': summary = request.GET['summary'] description = request.GET['description'] scanner = request.GET['scanner'] vuln_id = request.GET['vuln_id'] scan_id = request.GET['scan_id'] return render( request, 'submit_jira_ticket.html', { 'jira_projects': jira_projects, 'summary': summary, 'description': description, 'scanner': scanner, 'vuln_id': vuln_id, 'scan_id': scan_id }) if request.method == 'POST': summary = request.POST.get('summary') description = request.POST.get('description') project_id = request.POST.get('project_id') issue_type = request.POST.get('issue_type') vuln_id = request.POST.get('vuln_id') scanner = request.POST.get('scanner') scan_id = request.POST.get('scan_id') issue_dict = { 'project': { 'id': project_id }, 'summary': summary, 'description': description, 'issuetype': { 'name': issue_type }, } new_issue = jira_ser.create_issue(fields=issue_dict) # print new_issue if scanner == 'zap': zap_scan_results_db.objects.filter(vuln_id=vuln_id).update( jira_ticket=new_issue) return HttpResponseRedirect( '/webscanners/zap_vul_details/?scan_id=%s&scan_name=%s' % (scan_id, summary)) elif scanner == 'burp': burp_scan_result_db.objects.filter(vuln_id=vuln_id).update( jira_ticket=new_issue) return HttpResponseRedirect( '/webscanners/burp_vuln_out/?scan_id=%s&scan_name=%s' % (scan_id, summary)) elif scanner == 'arachni': arachni_scan_result_db.objects.filter(vuln_id=vuln_id).update( jira_ticket=new_issue) return HttpResponseRedirect( '/webscanners/arachni_vuln_out/?scan_id=%s&scan_name=%s' % (scan_id, summary)) elif scanner == 'open_vas': ov_scan_result_db.objects.filter(vul_id=vuln_id).update( jira_ticket=new_issue) return HttpResponseRedirect( '/networkscanners/vul_details/?scan_id=%s' % scan_id) elif scanner == 'nessus': nessus_report_db.objects.filter(vul_id=vuln_id).update( jira_ticket=new_issue) return HttpResponseRedirect( '/networkscanners/nessus_vuln_details/?scan_id=%s' % scan_id)
jira = JIRA(parsedArgs['server'], basic_auth=(parsedArgs['user'], parsedArgs['password'])) #if True: if False: issueDict = dict({ 'project': { 'key': parsedArgs['key'] }, 'issuetype': { 'name': 'Test Plan' }, 'summary': 'Test Test Plan' }) issue = jira.create_issue(fields=issueDict) issueDict = dict({ 'project': { 'key': parsedArgs['key'] }, 'issuetype': { 'name': 'Test Case' }, 'parent': { 'id': issue.key }, 'customfield_11493': 'SBREST-358', 'summary': 'test it', }) issue = jira.create_issue(fields=issueDict) jira.transition_issue(issue, 'Test')
class JiraClient: def __init__(self, options, basic_auth, project): self.jira = JIRA(options, basic_auth=basic_auth) self.project = project def get_issues_by_summary(self, summary): """ Find issues by using the summary (issue title) Args: summary Return: A list of issues """ try: issues = self.jira.search_issues( "project={0} AND summary ~ '{1}'".format( self.project, summary)) except Exception: raise return issues def get_issue_by_key(self, key): """ Find issue by using the key (e.g BEAM-1234) Args: key Return: issue """ try: issue = self.jira.issue(key) except Exception: raise return issue def create_issue(self, summary, components, description, issuetype='Bug', assignee=None, parent_key=None): """ Create a new issue Args: summary - Issue title components - A list of components description (optional) - A string that describes the issue issuetype (optional) - Bug, Improvement, New Feature, Sub-task, Task, Wish, etc. assignee (optional) - A string of JIRA user name parent_key (optional) - The parent issue key is required when creating a subtask. Return: Issue created """ fields = { 'project': { 'key': self.project }, 'summary': summary, 'description': description, 'issuetype': { 'name': issuetype }, 'components': [], } for component in components: fields['components'].append({'name': component}) if assignee is not None: fields['assignee'] = {'name': assignee} if parent_key is not None: fields['parent'] = {'key': parent_key} fields['issuetype'] = {'name': 'Sub-task'} try: new_issue = self.jira.create_issue(fields=fields) except Exception: raise return new_issue def update_issue(self, issue, summary=None, components=None, description=None, assignee=None, notify=True): """ Create a new issue Args: issue - Jira issue object summary (optional) - Issue title components (optional) - A list of components description (optional) - A string that describes the issue assignee (optional) - A string of JIRA user name notify - Query parameter notifyUsers. If true send the email with notification that the issue was updated to users that watch it. Admin or project admin permissions are required to disable the notification. Return: Issue created """ fields = {} if summary: fields['summary'] = summary if description: fields['description'] = description if assignee: fields['assignee'] = {'name': assignee} if components: fields['components'] = [] for component in components: fields['components'].append({'name': component}) try: issue.update(fields=fields, notify=notify) except Exception: raise def reopen_issue(self, issue): """ Reopen an issue Args: issue - Jira issue object """ try: self.jira.transition_issue(issue.key, 3) except: raise
# queries to find other created N&N Task issues, not issues for which N&N should be written nnissuesqueryall = 'summary ~ "New and Noteworthy" AND project in (JBIDE, JBDS) ORDER BY key DESC' nnissuesquerythisversion = 'summary ~ "New and Noteworthy" AND ((project in (JBDS) and fixVersion = "' + jbds_fixversion + '") or (project in (JBIDE) and fixVersion = "' + jbide_fixversion + '")) ORDER BY key DESC' rootnn_description = 'This [query|' + nnsearch + '] contains the search for all N&N. See subtasks below.' rootnn_dict = { 'project' : { 'key' : 'JBIDE' }, 'summary' : 'Create New and Noteworthy for ' + jbide_fixversion, 'description' : rootnn_description, 'issuetype' : { 'name' : 'Task' }, 'priority' : { 'name' :'Blocker'}, 'fixVersions' : [{ "name" : jbide_fixversion }], 'components' : [{ "name" : "website" }] } rootnn = jira.create_issue(fields=rootnn_dict) componentLead = defaultAssignee() try: jira.assign_issue(rootnn, componentLead) except: print "[WARNING] Unexpected error! User {0} tried to assign {1} to {2}: {3}".format(options.usernameJIRA, rootnn, componentLead, sys.exc_info()[0]) print("JBoss Tools : " + jiraserver + '/browse/' + rootnn.key + " => " + componentLead + "") def nametuple(x): return { "name" : x } def quote(x): return '"' + x + '"' # see JIRA_components listing in components.py
'\n\n[Search for all task JIRA|' + tasksearch + ']', 'issuetype': { 'name': 'Task' }, 'priority': { 'name': 'Blocker' }, 'fixVersions': [{ "name": jbds_fixversion }], 'components': [{ "name": "installer" }], 'labels': ["task"], } rootJBDS = jira.create_issue(fields=rootJBDS_dict) installerLead = queryComponentLead(CLJBDS, 'installer', 0) try: jira.assign_issue(rootJBDS, installerLead) except: if (not options.jiraonly): print "[WARNING] Unexpected error! User {0} tried to assign {1} to {2}: {3}".format( options.jirauser, rootJBDS, installerLead, sys.exc_info()[0]) if (options.jiraonly): print(rootJBDS.key) else: print("Task JIRA created for this milestone include:") print("") print("JBDS : " + jiraserver + '/browse/' + rootJBDS.key + " => " + installerLead)
def main_function(): # Find your Account SID and Auth Token at twilio.com/console # and set the environment variables. See http://twil.io/secure account_sid = os.environ['TWILIO_ACCOUNT_SID'] auth_token = os.environ['TWILIO_AUTH_TOKEN'] client = Client(account_sid, auth_token) # Extract new recordings and then delete them recordings = client.recordings.list() for recording in recordings: recording_id = recording.sid url = f"https://api.twilio.com/{recording.uri.replace('.json', '.wav')}" r = requests.get(url, allow_redirects=True) open(f'unprocessed_recordings/{recording_id}.wav', 'wb').write(r.content) recording.delete() # Save all transcripts in a CSV file current_directory = os.path.dirname(os.path.realpath("__file__")) + "/" unprocessed_directory = os.path.join(current_directory, 'unprocessed_recordings') processed_directory = os.path.join(current_directory, 'processed_recordings') tickets_to_make_directory = os.path.join(current_directory, 'tickets_to_make') tickets_to_make_file = f"{tickets_to_make_directory}/data.csv" wav_files = glob.glob(f"{unprocessed_directory}/*") for wav_file in wav_files: command = react_to_recording(wav_file) command = "" if command == None else command.lower() response = command append_ideas_to_list(response, tickets_to_make_file) os.rename( wav_file, wav_file.replace( "unprocessed_recordings/", "processed_recordings/", )) # Write each record in the CSV file as a JIRA ticket jira_user = os.environ['JIRA_USER'] jira_apikey = os.environ['JIRA_TOKEN'] jira_server = os.environ['JIRA_SERVER'] jira = JIRA(basic_auth=(jira_user, jira_apikey), options={'server': jira_server}) df = pd.read_csv(tickets_to_make_file) for index, row in df.iterrows(): idea = str(row['Ideas']) # Ticket issue_dict = { 'project': "DS", 'summary': idea if len(idea) < 120 else "New Ticket", 'description': "" if len(idea) < 120 else idea, 'issuetype': { 'name': 'Story' }, } _ = jira.create_issue(issue_dict) os.remove(tickets_to_make_file) tickets_made = len(wav_files) speak_text( f"Created {tickets_made} ticket{'s' if tickets_made != 1 else ''}")
logger.info("{0} Jira bugs were found".format(len(Jbugs))) logger.info("{0} Launchpad bugs were found".format(len(lp_bugs))) for Lbug in lp_bugs: m = str(Lbug.milestone).replace('https://api.launchpad.net/' + lp_api + '/' + lp_project + '/+milestone/', '') logger.info("{0} milestone: {1}".format(Lbug.title.encode('utf-8'), m)) it_created = False for Jbug in Jbugs: if str(Lbug.bug.id) in Jbug.fields.summary: for ver in Jbug.fields.fixVersions: if milestones[m] in ver.name: logger.info("Matched to Jira issue {0} ({1})".format( Jbug.key, Jbug.fields.summary.encode('utf-8'))) it_created = True sync_jira_status(Jbug, Lbug) break if not it_created and not Lbug.bug.duplicate_of and Lbug.status not in \ ["Won't Fix", 'Invalid', 'Fix Released']: summary = Lbug.title newJbug = jira.create_issue(project=jira_project, summary=summary, description=Lbug.web_link, labels=['launchpad'], issuetype={'name': 'Bug'}) logger.info("Jira issue {0} ({1}) was successfully added".format( newJbug.key, newJbug.fields.summary.encode('utf-8'))) issue_dict = {"fixVersions": [{"name": milestones[m]}]} newJbug.update(fields=issue_dict) sync_jira_status(newJbug, Lbug)
places.append("Akai Hana") places.append("Brett's BBQ") places.append("Which Wich") places.append("Rubios") places.append("Tandoori Xpress") places.append("Cana Cafe") places.append("Donut Touch") places.append("New York Bagels") places.append("Karl Strauss") number = random.randint(0,len(places) - 1) sc.rtm_send_message(chan, "You should go to %s to for food." % places[number]) ####JIRA STUFF elif "!helpdesk" in message: request = message[10:] new_issue = j.create_issue(project="IT", summary=request, description="Created by Slack", issuetype={'name':'Service Request'}, reporter={"name": email}) #edit project ID to match. sc.rtm_send_message(chan, "Your helpdesk ticket for '%s' has been created." % request) ####Hacker News Stuff elif "!hn" in message: n=0 sc.rtm_send_message(chan,"Top 2 HackerNews Stories:") for story_id in hn.top_stories(limit=2): derp = hn.get_item(story_id) derp = str(derp) print "derp is:" print derp herp = derp print "herp is:" print herp derpy = derp.split(":")[1] print "derpy is:"
def update_or_create_jira_issue(user_token, is_curator): try: params = app.config.get('JIRA_PARAMS') user_name = params['username'] password = params['password'] default_curator = 'metabolights-api' updated_studies = [] try: jira = JIRA(options=options, basic_auth=(user_name, password)) except: return False, 'Could not connect to JIRA server, incorrect username or password?', updated_studies # Get the MetaboLights project mtbls_project = jira.project(project) if is_curator: studies = get_all_studies(user_token) for study in studies: study_id = None user_name = None release_date = None update_date = None study_status = None curator = None status_change = None curation_due_date = None try: study_id = safe_str(study[0]) user_name = safe_str(study[1]) release_date = safe_str(study[2]) update_date = safe_str(study[3]) study_status = safe_str(study[4]) curator = safe_str(study[5]) status_change = safe_str(study[6]) curation_due_date = safe_str(study[7]) except Exception as e: logger.error(str(e)) issue = [] summary = None # date is 'YYYY-MM-DD HH24:MI' due_date = status_change[:10] logger.info('Checking Jira ticket for ' + study_id + '. Values: ' + user_name + '|' + release_date + '|' + update_date + '|' + study_status + '|' + curator + '|' + status_change + '|' + due_date) # Get an issue based on a study accession search pattern search_param = "project='" + mtbls_project.key + "' AND summary ~ '" + study_id + " \\\-\\\ 20*'" issues = jira.search_issues(search_param) # project = MetaboLights AND summary ~ 'MTBLS121 ' new_summary = study_id + ' - ' + release_date.replace('-', '') + ' - ' + \ study_status + ' (' + user_name + ')' try: if issues: issue = issues[0] else: if study_status == 'Submitted' or study_status == 'In Curation': logger.info("Could not find Jira issue for " + search_param) print("Creating new Jira issue for " + search_param) issue = jira.create_issue(project=mtbls_project.key, summary='MTBLS study - To be updated', description='Created by API', issuetype={'name': 'Story'}) else: continue # Only create new cases if the study is in status Submitted/In Curation except: # We could not find or create a Jira issue. continue summary = issue.fields.summary # Follow pattern 'MTBLS123 - YYYYMMDD - Status' if not summary.startswith('MTBLS'): continue # Skip all cases that are not related the study accession numbers try: assignee = issue.fields.assignee.name except: assignee = "" assignee_changed = False valid_curator = False jira_curator = "" if curator: if curator.lower() == 'mark': jira_curator = 'mwilliam' valid_curator = True elif curator.lower() == 'pamela': jira_curator = 'ppruski' valid_curator = True elif curator.lower() == 'xuefei' or curator.lower() == 'reza' or curator.lower() == 'keeva': jira_curator = default_curator # We do not have a current curation listed in the log valid_curator = True assignee_changed = True if assignee != jira_curator else False else: jira_curator = "" if not status_change: status_change = "No status changed date reported" # Release date or status has changed, or the assignee (curator) has changed summary_changed = True if summary != new_summary else False curator_update = True if assignee != default_curator and jira_curator != default_curator else False if assignee_changed or summary_changed: # Add "Curation" Epic issues_to_add = [issue.key] jira.add_issues_to_epic(curation_epic, issues_to_add) # Add the Curation Epic labels = maintain_jira_labels(issue, study_status, user_name) # Add a comment to the issue. comment_text = 'Current status ' + study_status + '. Status last changed date ' + status_change + \ '. Curation due date ' + due_date + '. Database update date ' + update_date if jira_curator == default_curator: comment_text = comment_text + '. Default curator has been changed from "' \ + curator + '" to "' + default_curator + '"' if assignee_changed: comment_text = comment_text + '. Curator in Jira changed from "' + assignee + '" to "' + jira_curator + '"' if summary_changed: comment_text = comment_text + '. Summary in Jira changed from "' + summary + '" to "' + new_summary + '"' jira.add_comment(issue, comment_text) # Change the issue's summary, comments and description. issue.update(summary=new_summary, fields={"labels": labels}, notify=False) # if valid_curator: # ToDo, what if the curation log is not up to date? issue.update(assignee={'name': jira_curator}, notify=False) updated_studies.append(study_id) logger.info('Updated Jira case for study ' + study_id) print('Updated Jira case for study ' + study_id) except Exception as e: logger.error("Jira updated failed for " + study_id + ". " + str(e)) return False, 'Update failed: ' + str(e), str(study_id) return True, 'Ticket(s) updated successfully', updated_studies
class JiraAPI: """ Jira client has no documentation, so if you need one, use one for REST API: https://developer.atlassian.com/cloud/jira/platform/rest/v3/ """ def __init__(self, settings: Settings): self._settings = settings self.transition = settings.jira.transition self.release_task = settings.jira.release_task self.release_task_name = self.release_task.name.format( version=settings.version, component=self.release_task.component) self._api = JIRA( {"server": settings.jira.connection.server}, basic_auth=(settings.jira.connection.user, settings.jira.connection.token), ) def _create_version(self, project: Project): proposed_name = "Hotfix" if self._settings.version.minor > 0 else "Release" user_input = input(f"Input new Jira version name: [{proposed_name}]: ") name = user_input if user_input else proposed_name return self._api.create_version(name, project, startDate=_get_formatted_date()) def _select_version(self, project: Project, unreleased_versions) -> Optional[Version]: print("Jira versions:") print("1) Skip") print("2) Create new") print("or select existing one:") unreleased_versions = { idx: version for idx, version in enumerate(unreleased_versions, 3) } for idx, version in unreleased_versions.items(): print(f"{idx}) {version.name}") user_input = 0 valid_choices = list(range(1, len(unreleased_versions) + 3)) while user_input not in valid_choices: try: user_input = int( input( "\nChoose which Jira version use for this release: ")) except Exception: continue if user_input == 1: return None elif user_input == 2: return self._create_version(project) else: return unreleased_versions[user_input] def get_version(self) -> Optional[Version]: print_title(f"Searching for Jira release version") project = self._api.project(self.release_task.project) unreleased_versions = [ v for v in self._api.project_versions(project) if not v.released ] return self._select_version(project, unreleased_versions) def _get_jira_release_unfinished_tasks(self, version: Version): """ To check that all tasks in Jira release is finished select them using jql """ final_statuses = '", "'.join(self.transition.child_final_statuses) types_to_skip = '", "'.join(self.transition.child_task_types_to_skip) return self._api.search_issues( f'project = "{self.release_task.project}"' f' AND fixVersion = "{version.name}"' f' AND fixVersion in unreleasedVersions("{self.release_task.project}")' f' AND status NOT IN ("{final_statuses}")' f' AND type NOT IN ("{types_to_skip}")') def _get_transition(self, issue, transition_name): transitions = [ t for t in self._api.transitions(issue) if t["name"].lower() == transition_name.lower() ] if not transitions: return None return transitions[0] def release_version(self, release_task_key: str): print_title( f"Releasing Jira version of release task {release_task_key}") release_task = self._api.issue(release_task_key) for version in release_task.fields.fixVersions: version: Version print(f'Checking Jira release version: "{version.name}"...') if version.released: print_error("Version is already released") continue unfinished_tasks = self._get_jira_release_unfinished_tasks(version) if unfinished_tasks: tasks_str = ", ".join([i.key for i in unfinished_tasks]) print_error( f'Can\'t release Jira version: "{version.name}", it has unfinished tasks: {tasks_str}' ) continue print("Jira version is safe to release, releasing...", end=" ") version.update(released=True, releaseDate=_get_formatted_date()) print("Ok!") def _add_to_release_version(self, version: Version, release_task_key: str): issue = self._api.issue(release_task_key) issue.add_field_value("fixVersions", {"name": version.name}) def make_links(self, version: Optional[Version], release_task_key, related_keys): version_name = version.name if version else "-" print_title(f"Linking tasks found in release branch" f" to release task ({release_task_key})" f' and to Jira version "{version_name}"') if version: self._add_to_release_version(version, release_task_key) print(f"Linking {len(related_keys)} tasks:") partial_make_links = partial(self._make_links, version, release_task_key) with ThreadPool(5) as pool: pool.map(partial_make_links, related_keys) def _make_links(self, version: Optional[Version], release_task_key: str, child_task_key: str): print(f"* {child_task_key}") self._api.create_issue_link(self.release_task.link_type, release_task_key, child_task_key) if version: self._add_to_release_version(version, child_task_key) def make_release_task(self): print_title("Creating Jira release task") extra_fields = {"components": [{"name": self.release_task.component}]} issue = self._api.create_issue( project=self.release_task.project, summary=self.release_task_name, issuetype={"name": self.release_task.type}, **extra_fields, ) print(f"Created Jira release task: {issue.key}") return issue.key def get_release_task(self): print_title("Searching for Jira release task") query = (f'project = "{self.release_task.project}"' f' AND summary ~ "{self.release_task_name}"' f' AND type = "{self.release_task.type}"') found_issues = self._api.search_issues(query) if not found_issues: print("Did not find existing release task") return self.make_release_task() if len(found_issues) > 1: issues_str = ", ".join([i.key for i in found_issues]) print_error( f"Your release task has not unique name, fix it before using this functionality," f" found issues: {issues_str}") exit(1) release_issue = found_issues[0] print(f"Found Jira release task: {release_issue.key}") return release_issue.key def mark_release_task_done(self, release_task_key): print_title( f'Transition release task "{release_task_key}" from "{self.transition.release_from_status}" to "{self.transition.release_to_status}"' ) release_issue = self._api.issue(release_task_key) print_title( f'Current release task status is "{release_issue.fields.status}"') if (release_issue.fields.status.name.lower() != self.transition.release_from_status.lower()): print_error( f'Release task "{release_task_key}" has inproper status') return transition = self._get_transition(release_issue, self.transition.release_to_status) if not transition: print_error( f'Release task "{release_task_key}" has no transition to "{self.transition.release_to_status}"' ) return self._api.transition_issue(release_issue, transition["id"]) print( f'Release task {release_issue.key} has been transited to status "{transition["name"]}"' ) def mark_children_tasks_done(self, release_task_key): print_title( f'Transition children of "{release_task_key}" from "{self.transition.child_from_status}" to "{self.transition.child_to_status}"' ) query = (f'issue in linkedIssues("{release_task_key}")' f' AND status = "{self.transition.child_from_status}"') found_issues = self._api.search_issues(query) to_status = self.transition.child_to_status.lower() if not found_issues: print("Did not find any task for transition") return for issue in found_issues: transition = self._get_transition(issue, to_status) if not transition: print_error( f'Issue "{issue.key}" does not have transition to status "{self.transition.child_to_status}"' ) continue self._api.transition_issue(issue, transition["id"]) print( f'Task {issue.key} has been transited to status "{transition["name"]}"' )
class JiraLocal(object): def __init__(self, jira_url, auth_user, auth_token, rule, jira_fields_dict): self.jira_instance = JIRA(jira_url, basic_auth=(auth_user, auth_token)) self.jira_config = rule['jira_config'] self.jira_fields_dict = jira_fields_dict self.jira_issue_id_field_key = jira_fields_dict[ rule['jira_config']["jira_issue_id_field"]] self.log = Logger(rule=rule) return def get_jira_issues(self, project_key, halo_issues): jira_issues_dict = {} with ThreadPoolExecutor(max_workers=os.cpu_count() * 2) as executor: future_to_issue_id = { executor.submit(self.get_jira_issues_for_halo_issue, issue["id"], project_key): issue["id"] for issue in halo_issues } for future in as_completed(future_to_issue_id): issue_id = future_to_issue_id[future] jira_issues = future.result() jira_issues_dict[issue_id] = jira_issues return jira_issues_dict def get_jira_epics_or_issues(self, project_keys, issuetype, dict_format=True): if isinstance(project_keys, str): project_keys = [project_keys] jira_issues_dict = defaultdict(list) jira_issues = self.jira_instance.search_issues( f'project in ({", ".join(x for x in project_keys)}) AND ' f'resolution = Unresolved AND ' f'issuetype={issuetype} AND ' f'"{self.jira_config["jira_issue_id_field"]}" is not EMPTY', maxResults=False) if not dict_format: return jira_issues for issue in jira_issues: jira_issues_dict[issue.raw["fields"][ self.jira_issue_id_field_key]].append(issue) return jira_issues_dict def get_jira_issues_for_halo_issue(self, issue_id, project_key): results = self.jira_instance.search_issues( f'project="{project_key}" AND ' f'"{self.jira_config["jira_issue_id_field"]}"~{issue_id} AND ' f'issuetype="{self.jira_config["jira_issue_type"]}"') return results def create_jira_epic(self, group_key_hash, group_key_str, project_key): # Get IDs for epic fields epic_dict = { 'project': { 'key': project_key }, 'summary': group_key_str, self.jira_fields_dict["Epic Name"]: group_key_str, 'description': group_key_str, 'issuetype': { 'name': 'Epic' }, self.jira_issue_id_field_key: group_key_hash } epic = self.jira_instance.create_issue(fields=epic_dict) return epic def create_jira_issue(self, issue, epic, jira_fields_dict, fields, project_key): epic_link = None if epic: epic_link = epic.key summary, description, field_mapping = self.prepare_issue( issue, fields, jira_fields_dict) issue_dict = { 'project': { 'key': project_key }, 'issuetype': { 'name': self.jira_config['jira_issue_type'] }, self.jira_issue_id_field_key: issue["id"], self.jira_fields_dict["Epic Link"]: epic_link, 'summary': summary, 'description': description } issue_dict.update(field_mapping) self.log.info(f"Creating issue: {issue['id']}") self.jira_instance.create_issue(fields=issue_dict) def update_jira_issue(self, issue, jira_issues, jira_fields_dict, fields): self.log.info(f"Updating issue: {issue['id']}") for jira_issue in jira_issues: summary, description, field_mapping = self.prepare_issue( issue, fields, jira_fields_dict) issue_dict = {'summary': summary, 'description': description} issue_dict.update(field_mapping) jira_issue.update(fields=issue_dict) if issue["status"] == "resolved": self.transition_issue(jira_issue, self.jira_config["issue_status_closed"]) elif jira_issue.raw["fields"]["status"][ "name"] == self.jira_config["issue_status_closed"]: self.transition_issue( jira_issue, self.jira_config["issue_status_reopened"]) def transition_issue(self, issue, transition_name): self.log.info(f"Transitioning issue {issue.key} to {transition_name}") transition_id = self.jira_instance.find_transitionid_by_name( issue, transition_name) try: self.jira_instance.transition_issue(issue, transition_id) except JIRAError: self.log.error( f"Could not transition Jira Issue '{issue.key}' " f"from {issue.raw['fields']['status']['name']} to {transition_name}" ) def prepare_issue(self, issue, fields, jira_fields_dict): asset_formatted = Formatter.format_object(issue["asset_type"], issue.pop("asset")) finding_formatted = Formatter.format_object("findings", issue.pop("findings")) issue_formatted = Formatter.format_object("issue", issue) summary = Formatter.format_summary(issue) description = issue_formatted + asset_formatted + finding_formatted description = description[:32759] + '{code}\n\n' dynamic_map = fields.get("mapping") or {} static = fields.get("static") or {} field_mapping = map_fields(dynamic_map, static, issue, jira_fields_dict) return summary, description, field_mapping def push_issues(self, issues, jira_epics_dict, jira_issues_dict, jira_fields_dict, fields, project_key=None): with ThreadPoolExecutor(max_workers=os.cpu_count() * 2) as executor: for issue in issues: jira_issues = jira_issues_dict.get(issue["id"]) if jira_issues: executor.submit(self.update_jira_issue, issue, jira_issues, jira_fields_dict, fields) else: groupby_key = issue.pop("groupby_key", "") epic = jira_epics_dict.get(groupby_key) executor.submit(self.create_jira_issue, issue, epic, jira_fields_dict, fields, project_key) def cleanup_epics(self, project_keys): jira_issues = self.get_jira_epics_or_issues( project_keys, self.jira_config["jira_issue_type"], dict_format=False) epics_set = set(issue.raw["fields"][self.jira_fields_dict["Epic Link"]] for issue in jira_issues) jira_epics = self.get_jira_epics_or_issues(project_keys, "Epic", dict_format=False) with ThreadPoolExecutor(max_workers=os.cpu_count() * 2) as executor: for epic in jira_epics: if epic.key not in epics_set: self.log.info(f"Deleting epic: {epic.key}") executor.submit(self.transition_issue, epic, self.jira_config["issue_status_closed"])
def process_event(helper, *args, **kwargs): helper.log_info("Alert action jirable started.") # addinfo adds _search_et, _search_lt, _timestamp to helper.info # we use these fields for the kvstore, so grab them up here (just once) helper.addinfo() # jirable.py checks for the presence of these mandatory settings, so don't bother doing so here jira_url = helper.get_global_setting("jira_url") username = helper.get_global_setting("username") password = helper.get_global_setting("password") dynamic_field_prefix = helper.get_global_setting("dynamic_field_prefix") # unique_id_field_name is optional, and is not checked in jirable.py unique_id_field_name = helper.get_global_setting("unique_id_field_name") # try to connect, bail out if unable to do so jira = None try: jira = JIRA(jira_url, basic_auth=(username, password)) except: helper.log_info( "Unable to connect to JIRA. Check URL and authentication settings." ) return 1 # The following example gets the alert action parameters and prints them to the log project = helper.get_param("project") helper.log_info("project={}".format(project)) drilldown_dashboard = helper.get_param("drilldown_dashboard") helper.log_info("drilldown_dashboard={}".format(drilldown_dashboard)) unique_id_value = helper.get_param("unique_id_value") helper.log_info("unique_id_value={}".format(unique_id_value)) issue_type = helper.get_param("issue_type") helper.log_info("issue_type={}".format(issue_type)) summary = helper.get_param("summary") helper.log_info("summary={}".format(summary)) dedup_by_unique_id_value = helper.get_param("dedup_by_unique_id_value") helper.log_info( "dedup_by_unique_id_value={}".format(dedup_by_unique_id_value)) drilldown_search = helper.get_param("drilldown_search") helper.log_info("drilldown_search={}".format(drilldown_search)) # quit if asked to dedup without the unique field name # note that "yes" is hardcoded, and this is the string that must be in savedsearches.conf # the use of 0, false, etc is not supported if dedup_by_unique_id_value == "yes" and not unique_id_field_name: helper.log_info( "Dedup by Unique ID Value was checked, but Unique ID Field Name is not set. Bailing out." ) return 1 # JIRA forces setting customfields by customfield id instead of customfield name, so fetch the customfield info here to find our unique field id customfield_ids = {} for customfield in jira.fields(): customfield_ids[customfield['name']] = customfield['id'] # we fetched all field ids, but only set unique_customfield_id if we have unique_id_field_name events = helper.get_events() for event in events: templated_project = Template(project).render(**event) templated_issue_type = Template(issue_type).render(**event) templated_summary = Template(summary).render(**event) # what our new issue will look like (to start with) issue_fields = { 'project': templated_project, 'issuetype': { 'name': templated_issue_type }, 'summary': templated_summary, } # we need search_et, search_lt, and alert_time in several of the below blocks # _timestamp is the time the search was run alert_time = helper.info.get('_timestamp') # if no _search_et, use start of time (0) search_et = helper.info.get('_search_et', 0) # if no _search_lt, use the time of the search search_lt = helper.info.get('_search_lt', alert_time) # templated_drilldown_search goes into kvstore even when not using it to create a new issue, so define prior to those blocks # if not provided, it will be an empty string so no need to check for its presence first templated_drilldown_search = Template(drilldown_search).render(**event) # a new issue will be created if issue remains False issue = False # don't bother searching unless configured to dedup if dedup_by_unique_id_value == "yes": templated_unique_id_value = Template(unique_id_value).render( **event) unique_customfield_id = customfield_ids[unique_id_field_name] issue_fields[unique_customfield_id] = templated_unique_id_value # JIRA only allows CONTAINS searches against text fields, so we search for our full unique_id_value, then have to check for exact match against each found issue for existing_issue in jira.search_issues( '{} ~ "{}" and status!=Done and status!=Resolved'.format( unique_id_field_name, templated_unique_id_value.replace('"', '\\"'))): try: # this is apparently how you do something like existing_issue.$unique_customfield_id existing_issue_unique_id_value = getattr( existing_issue.fields, unique_customfield_id) if existing_issue_unique_id_value == templated_unique_id_value: issue = existing_issue jira_action = "append" except: # it's fine if the custom field doesn't exist, because we're not sure if the events that matched have the field pass # issue = False if not asked to dedup or if no matching dedup issue was found if not issue: issue = jira.create_issue(fields=issue_fields) jira_action = "create" dynamic_field_regex = re.compile(r"^" + dynamic_field_prefix + "(?P<dynamic_field_name>.*)$") for field in event: match = dynamic_field_regex.match(field) if match: try: issue.update( fields={ customfield_ids[match.group('dynamic_field_name')]: event[field] }) except: # in case a dynamic field was improperly named, etc, don't bail out, just log it helper.log_info("Unable to set field: {}".format( match.group('dynamic_field_name'))) # attach raw events if drilldown_search defined (only for new issues) if templated_drilldown_search: session_key = helper.session_key service = client.connect(token=session_key) # If the query doesn't already start with the 'search' operator or another # generating command (e.g. "| inputcsv"), then prepend "search " to it. # But leave templated_drilldown_search alone, so it gets added to the kvstore as entered in the alert params search_to_run = templated_drilldown_search if not (search_to_run.startswith('search') or search_to_run.startswith("|")): search_to_run = 'search ' + search_to_run job = service.jobs.create(search_to_run, earliest_time=search_et, latest_time=search_lt, exec_mode="blocking") raw_values = [] for result in results.ResultsReader(job.results()): raw_values.append(result['_raw']) full_raw = "\n".join(raw_values) jira.add_comment(issue, full_raw) # new or existing issue, we continue processing to add to the kvstore session_key = helper.session_key # need to set owner="nobody" to use kvstore service = client.connect(owner="nobody", token=session_key) jirables_collection = service.kvstore['jirables'] jirables_collection.data.insert( json.dumps({ "jira_key": issue.key, "alert_time": alert_time, "search_et": search_et, "search_lt": search_lt, "jira_action": jira_action, "drilldown_dashboard": drilldown_dashboard, "drilldown_search": templated_drilldown_search, })) return 0
class TaskJira(TaskManager): """Subclass of TaskManager implementing HMGM task management with Jira platforms.""" # Set up jira instance with properties in the given configuration instance. def __init__(self, config): super().__init__(config) # get Jira server info from the config jira_server = config["jira"]["server"] jira_username = config["jira"]["username"] jira_password = config["jira"]["password"] self.jira_project = config["jira"]["project"] self.jira = JIRA(server=jira_server, basic_auth=(jira_username, jira_password)) # Create a jira issue given the task_type, context, input/output json, # save information about the created issue into a JSON file, and return the issue. def create_task(self, task_type, context, editor_input, task_json): # populate the jira fields into a dictionary with information from task_type and context etc project = {"key": self.jira_project} issuetype = {"name": "Task"} labels = [task_type] summary = context["primaryfileName"] + " - " + context["workflowName"] description = self.get_task_description(task_type, context, editor_input) jira_fields = { "project": project, "issuetype": issuetype, "labels": labels, "summary": summary, "description": description } # create a new task jira using jira module issue = self.jira.create_issue(fields=jira_fields) # extract important information (ID, key, and URL) of the created issue into a dictionary, which is essentially the response returned by Jira server issue_dict = { "id": issue.id, "key": issue.key, "url": issue.permalink() } # write jira issue into task_json file to indicate successful creation of the task with open(task_json, "w") as task_file: json.dump(issue_dict, task_file) return issue # Close the jira issue specified in task_json by updating its status and relevant fields, and return the issue. def close_task(self, task_json): # read jira issue info from task_json into a dictionary with open(task_json, 'r') as task_file: issue_dict = json.load(task_file) # get the jira issue using id issue = self.jira.issue(issue_dict["id"]) # retrieve transition ID based on name = Done instead of hard coding it, in case the ID might be different transitions = self.jira.transitions(issue) transition = None for t in transitions: if t["name"] == "Done": # Done is the status when an issue is closed transition = t["id"] break # if transition is None, that means issue is already in Done status if transition is None: print("Issue " + issue.id + " is already Done, probably closed manually by someone") # otherwise update the jira status to Done via transition else: print("Transition issue " + issue.id + " to status " + transition) self.jira.transition_issue(issue, transition) return issue
if env == 'qa': rfd_dict['issuetype'] = {'name': 'Task'} rfd_dict['description'] = 'QA code review.' else: rfd_dict['issuetype'] = {'name': 'RFD'} rfd_dict['summary'] = 'Deploy ' + app.upper() + ' ' + \ version.upper() + ' to ' + env.upper() + '' rfd_dict['fixVersions'] = [{'name': '' + version.upper() + ''}] rfd_dict['priority'] = {'name': 'Medium'} rfd_dict['customfield_10121'] = { 'value': '' + targetenv.upper() + '' } rfd_dict['customfield_10636'] = '' + datetime + ':00.000-0700' rfd = jira.create_issue(fields=rfd_dict) print "Created RFD: %s" % rfd if verbose: print "RFD Dict" print rfd_dict if dba_needed: # Populate the KV pairs for the DBA RFD-subtask rfd_st_dba_dict['project'] = {'key': '' + app.upper() + ''} if env == 'qa': rfd_st_dba_dict['issuetype'] = {'name': 'Sub-task'} else: rfd_st_dba_dict['issuetype'] = {'name': 'RFD-subtask'} rfd_st_dba_dict['parent'] = {'key': '' + str(rfd) + ''}
class JiraApi(): str_jira_scheduled = "%Y-%m-%dT%H:%M:%S.0%z" def __init__(self, instance=None, approver_instance=None): self.instance = instance self.approver_instance = approver_instance @staticmethod def next_immediate_window_dates(): tz = pytz.timezone(app.config['CM_TZ']) now_utc = datetime.utcnow() now_tz = tz.localize(now_utc) start = None if now_tz.hour <= app.config['CM_DEADLINE_HOUR'] and now_tz.minute < app.config['CM_DEADLINE_MIN']: start = tz.localize(datetime(now_tz.year, now_tz.month, now_tz.day, app.config['CM_SAME_DAY_START_HOUR'])) else: delay_hours = timedelta(hours=app.config['CM_DEADLINE_MISSED_DELAY_HOURS']) start_day = now_tz + delay_hours start = tz.localize(datetime( start_day.year, start_day.month, start_day.day, app.config['CM_DEADLINE_MISSED_START_HOUR'])) end = start + timedelta(hours=app.config['CM_WINDOW_LEN_HOURS']) return start.strftime(JiraApi.str_jira_scheduled), \ end.strftime(JiraApi.str_jira_scheduled) @staticmethod def get_datetime_now(): tz = pytz.timezone(app.config['CM_TZ']) now = pytz.utc.localize(datetime.utcnow()).astimezone(tz) return now.strftime(JiraApi.str_jira_scheduled) def connect(self): options = {'server': app.config['JIRA_HOSTNAME'], 'verify': False, 'check_update': False} self.instance = JIRA(options, basic_auth=(app.config['JIRA_USERNAME'], app.config['JIRA_PASSWORD'])) self.approver_instance = JIRA(options, basic_auth=(app.config['JIRA_APPROVER_USERNAME'], app.config['JIRA_APPROVER_PASSWORD'])) @staticmethod def ticket_link(issue): return '<a href="{}/browse/{}">{}</a>'.format(app.config['JIRA_HOSTNAME'], issue.key, issue.key) def resolve(self, issue): self.instance.transition_issue( issue, app.config['JIRA_RESOLVE_TRANSITION_ID'], assignee={'name': app.config['JIRA_USERNAME']}, resolution={'id': app.config['JIRA_RESOLVE_STATE_ID']}) def defect_for_exception(self, summary_title, e): return self.instance.create_issue( project=app.config['JIRA_PROJECT'], summary='[auto-{}] Problem: {}'.format(current_user.username, summary_title), description="Exception: {}".format(e), customfield_13842=JiraApi.get_datetime_now(), customfield_13838= {"value": "No"}, customfield_13831 = [ {"value": "Quality"}, {"value": "Risk Avoidance"} ], issuetype={'name': 'Defect'})
class JiraClient(): jira_con = None board_details = None def __init__(self): self._get_jira_board_details() secrets = _get_secrets_from_netrc() if not secrets: err_msg = 'Unable to locate or load suitable `.netrc` file for JIRA integration' logger.error(err_msg) raise ValueError(err_msg) try: username, account, apikey = secrets.authenticators( self.jira_hostname) except TypeError: err_msg = 'JIRA Connection. Unable to find details for machine "{}" `.netrc` file.'.format( self.jira_hostname) logger.error(err_msg) raise ValueError(err_msg) self.jira_con = JIRA(options={'server': account}, basic_auth=(username, apikey)) _check_jira_con(self.jira_con, username) logger.debug('JIRA Connection. Details = {}'.format( self.jira_con.myself())) def _get_jira_board_details(self): # TODO read these in from a config file self.jira_hostname = 'mapaction.atlassian.net' self.project_key = 'PIPET' # The target column should be were the column where new issues are created self.target_column = '10110' self.common_task_fields = { 'project': self.project_key, 'issuetype': { 'id': '10235' } } def __del__(self): try: self.jira_con.kill_session() except (TypeError, AttributeError): pass def task_handler(self, fail_threshold, msg, task_referal=None): logger.debug( 'JiraClient.task_handler called with status="{}", and msg="{}"'. format(fail_threshold, msg)) assured_referal = self.ensure_task_referal_type( task_referal, msg, fail_threshold) if not assured_referal: logger.debug( 'JiraClient.task_handler; `None` value passed for task_referal parameter. Nothing to handle.' ) return unique_summary = assured_referal.get_task_unique_summary() task_desc = assured_referal.get_task_description() op_id = assured_referal.get_operation_id() j_issue = self.search_issue_by_unique_summary(unique_summary, op_id) if j_issue: # Update existing card and maybe move it back into "Doing" column self.update_jira_issue(j_issue, task_desc, fail_threshold) else: if fail_threshold > logging.INFO: # Create a new task self.create_new_jira_issue(unique_summary, task_desc, op_id) def ensure_task_referal_type(self, task_referal, msg, fail_threshold): """ Check whether or not the `task_referal` is an instance of TaskReferralBase object. If it is the object is then it is returned unchanged. If not then an generic TaskReferralBase will be created and returned. The value of `str(task_referal)` will be used. @param task_referal: An object that may or may not be a TaskReferralBase object. @returns: If the `task_referal` param is an instance of TaskReferralBase object, then `task_referal` is returned. If `task_referal` param is NOT an instance of TaskReferralBase AND fail_threshold is logging.ERROR then a new TaskReferralBase object is created (using `msg` and `str(task_referal)` for context). Else `None` is returned. """ if isinstance(task_referal, TaskReferralBase): logger.debug( 'JiraClient.ensure_task_referal_type found a TaskReferralBase object' ) return task_referal if task_referal and (fail_threshold > logging.WARNING): logger.debug( 'JiraClient.ensure_task_referal_type created a new TaskReferralBase object' ) return TaskReferralBase(None, msg=msg, other=str(task_referal)) logger.debug( 'JiraClient.ensure_task_referal_type passed "{}" but returned `None`' .format(str(task_referal))) return None def search_issue_by_unique_summary(self, search_summary, op_id): # Default if `op_id` is None jql_op_id = 'operational_id is EMPTY' if op_id: jql_op_id = 'operational_id ~ "{}"'.format(op_id) jql_str = 'project={} AND {} AND summary ~ "{}"'.format( self.project_key, jql_op_id, search_summary) found_issues = self.jira_con.search_issues(jql_str, maxResults=2) if found_issues: if len(found_issues) > 1: raise ValueError( 'More than one JIRA Issue found with the summary "{}". This suggests that additional' ' issues have been raised manualy on the board "{}". Please ensure that there is exactly' ' one issues with this summary, by deleting those which have not been created by the' ' user "{}"'.format( search_summary, self.project_key, self.jira_con.myself()['emailAddress'])) else: return found_issues[0] else: return None def create_new_jira_issue(self, unique_summary, task_desc, op_id): flds = self.common_task_fields.copy() flds['summary'] = unique_summary flds['description'] = task_desc # This is the JIRA API's field ID for operational_id. To work this out execute: # ``` # a = j.jira_con.createmeta(projectKeys=['PIPET'], issuetypeIds=[10235], expand='projects.issuetypes.fields') # print(a) # ``` # Then search the output for your custom field name. Doubtless there is a programmatic way to do this. flds['customfield_10234'] = op_id new_task = self.jira_con.create_issue(fields=flds) # new_task.update(fields={'operational_id':op_id}) # new_task.update(operational_id=op_id) print(new_task) # print('desc', new_task.fields.description) # print('opid', new_task.fields.operational_id) # for f in new_task.fields: # print('field itr', f) def update_jira_issue(self, j_issue, task_desc, fail_threshold): now_utc = pytz.utc.localize(datetime.now()) time_stamp = now_utc.strftime('%Y-%m-%d %H:%M:%S %Z%z') # prev_desc = if task_desc != j_issue.fields.description: j_issue.update(description=task_desc) if fail_threshold > logging.INFO: self.jira_con.add_comment( j_issue.id, 'This Issue was still current when MapChef was run at {}'. format(time_stamp))
# -*- coding: utf-8 -*- """ Created on Tue Aug 23 06:05:32 2016 @author: bkunneke """ # Great write up on how to do everything in JIRA here: # https://pythonhosted.org/jira/ from jira import JIRA jira = JIRA(server='https://te2web.atlassian.net', basic_auth=('user', 'pwd')) # a username/password tuple projects = jira.projects() # Gets a list of all projects # Create an issue new_issue = jira.create_issue(project='PROJ_key_or_id', summary='New issue from jira-python', description='Look into this one', issuetype={'name': 'Bug'}) # Find a specific issue issues = jira.search_issues("key = 'HIL-163'")
}, 'parent': { 'id': key }, 'assignee': { 'name': data[7] }, #'priority' : { 'name' : data[3]}, #'description' : data[9].decode('iso-8859-1').encode('utf8'), 'duedate': dueDate.isoformat().decode('iso-8859-1').encode('utf8') } if not issueId == 10501 or 283: further = { #'customfield_23431' : estDate.isoformat().decode('iso-8859-1').encode('utf8'), #'customfield_21331' : { 'value' : data[8]} } subtask = merge_two_dicts(subtask, further) if issueId == 292: subtask['components'] = [{'name': "Cendoc"}] elif component: subtask['components'] = [{'name': component}] #pdb.set_trace() child = jira.create_issue(fields=subtask) print(child.key)
class JiraAPI(object): """Access Jira api using python-jira project.""" def __init__(self, user, passwd, logger): """Init JiraApi object and logger.""" self.logger = logger self.jira = JIRA(server=config.JIRA_HOST, basic_auth=(user, passwd)) def get_boards(self): """Get Jira boards list as json.""" self.logger.info("Getting Jira boards") json = {'boards': []} boards = self.jira.boards() for board in boards: json.get('boards').append({'id': board.id, 'name': board.name}) return json # ############### # ### Sprints ### # ############### def _get_board_sprints(self, board_id): """Get sprints of a board as json.""" self.logger.info("Getting board {} sprints".format(board_id)) json = {'sprints': []} sprints = self.jira.sprints(board_id, extended=True) for sprint in sprints: # self.logger.debug("Sprint content: {}".format(sprint.__dict__)) json.get('sprints').append({ 'id': sprint.id, 'key': sprint.id, 'name': sprint.name, 'start': sprint.startDate, 'end': sprint.endDate }) return json def search_sprint(self, board_key, sprint_name): """Search a sprint in a board and returns its info.""" self.logger.info("Searching board {} sprint '{}'".format( board_key, sprint_name)) found_sprint = None sprints = self._get_board_sprints(board_key) for sprint in sprints.get('sprints'): if (sprint.get('name') == sprint_name): found_sprint = sprint break return found_sprint def create_sprint(self, board_id, sprint_name, start, end): """Create a sprint in a board with given start and end dates. Dates must be in Jira format. """ self.logger.info("Creating sprint {}".format(sprint_name)) sprint = self.jira.create_sprint(name=sprint_name, board_id=board_id, startDate=start, endDate=end) # self.logger.debug("Created sprint content {}".format(sprint.__dict__)) return sprint.id def start_sprint(self, sprint_id, sprint_name, start, end): """Start a Jira sprint with given dates. *** Does not work because Jira API call fails with 'state' param. *** Dates must be in Jira format. """ self.logger.info("Starting sprint {} [Not working]".format(sprint_id)) # self.jira.update_sprint(sprint_id, name=sprint_name, startDate=start, endDate=end, state="active") def close_sprint(self, sprint_id, sprint_name, start, end): """Closes a Jira sprint with given dates. *** Does not work because Jira API call fails with 'state' param. *** Dates must be in Jira format. """ self.logger.info("Closing sprint {} [Not working]".format(sprint_id)) # self.jira.update_sprint(sprint_id, name=sprint_name, startDate=start, endDate=end, state=None) def delete_board_sprints(self, board_id): """Delete all sprints of a board.""" self.logger.info("Deleting board {} sprints".format(board_id)) sprints = self.jira.sprints(board_id, extended=False) for sprint in tqdm(sprints): self.logger.info("Deleting sprint {}".format(sprint.name)) sprint.delete() def _get_board_project_id(self, board_id): """Get the project id of a board.""" self.logger.debug("Getting project id of board {}".format(board_id)) project_id = None boards = self.jira.boards() for board in boards: # self.logger.debug("Comparing boards {}-{}".format(board.id, board_id)) if str(board.id) == str(board_id): # self.logger.debug("Found matching board {}".format(board.raw)) project_id = board.raw.get("filter").get("queryProjects").get( "projects")[0].get("id") break return project_id # #################### # ### User stories ### # #################### def _get_project_user_stories(self, project_key): """Get all user stories of a project.""" self.logger.info("Getting project {} user stories".format(project_key)) issues = self.jira.search_issues('project=' + project_key + ' and issuetype=' + config.JIRA_USER_STORY_TYPE, maxResults=200) return issues def delete_all_project_user_stories(self, project_key): """Delete all user stories of a project.""" self.logger.info( "Deleting project {} user stories".format(project_key)) issues = self._get_project_user_stories(project_key) for issue in tqdm(issues): self.logger.info("Deleting user story {}".format(issue)) issue.delete() def _create_user_story(self, project_id, subject, description, tags, points): """Create a user story with provided information.""" story_fields = { "project": project_id, "issuetype": config.JIRA_USER_STORY_TYPE, "summary": subject, "description": description, "labels": tags, config.JIRA_ESTIMATION_FIELD: points } created_story = self.jira.create_issue(fields=story_fields) self.logger.info("Created user story {} # {}".format( created_story.id, created_story.key)) # self.logger.debug("Created user story details: {}".format(created_story.__dict__)) return created_story def update_user_story_status(self, user_story_key, is_closed, status): """Update the status of a user story to Done or Not Done only if it's closed. A translation is made between the status given, which is the status in Taiga and the Jira status as defined in JIRA_USER_STORY_STATUS_DONE, JIRA_USER_STORY_STATUS_NOT_DONE and TAIGA_USER_STORY_STATUS_NOT_DONE constants of config.py file. """ self.logger.info( "Updating user story {} with Taiga status={} ({})".format( user_story_key, status, is_closed)) if is_closed: task_status = config.JIRA_USER_STORY_STATUS_DONE if status == config.TAIGA_USER_STORY_STATUS_NOT_DONE: task_status = config.JIRA_USER_STORY_STATUS_NOT_DONE self.jira.transition_issue(user_story_key, task_status) self.logger.info("Updated user story {} status to {}".format( user_story_key, task_status)) else: self.logger.warn( "Not updated user story {} beacuse is not closed: is_closed={}" .format(user_story_key, is_closed)) # ######################## # ### User story tasks ### # ######################## def _create_user_story_task(self, project_id, user_story_id, subject, description, finished_date): """Create a task inside a user story. The story task type is defined in config.py in JIRA_USER_STORY_TASK_TYPE constant. default is 'Sub-type'. """ self.logger.info("Creating user story task {}".format(subject)) created_task = self.jira.create_issue( project=project_id, parent={"id": user_story_id}, issuetype=config.JIRA_USER_STORY_TASK_TYPE, summary=subject) self.logger.info("Created user story task {} # {}".format( created_task.id, created_task.key)) # self.logger.debug("Created story task details: {}".format(created_task.__dict__)) return created_task def _update_task_status(self, task_key, status): """Update task status with Done or not Done status depending on incoming Taiga status. Taiga done status is defined in TAIGA_TASK_STATUS_DONE constant inside config.py file. Jira done and not done statuses are defined in JIRA_TASK_STATUS_DONE and JIRA_TASK_STATUS_NOT_DONE inside config.py file. """ self.logger.info( "Updating user story task {} with Taiga status={}".format( task_key, status)) task_status = config.JIRA_TASK_STATUS_DONE if status != config.TAIGA_TASK_STATUS_DONE: task_status = config.JIRA_TASK_STATUS_NOT_DONE self.jira.transition_issue(task_key, task_status) self.logger.info("Updated user story task {} status to {}".format( task_key, task_status)) # ####################### # ### Sprint creation ### # ####################### def _add_comment(self, issue_id, comment): """Add a comment to an issue.""" self.logger.debug("Adding comment to issue {}".format(issue_id)) self.jira.add_comment(issue_id, comment, visibility={'group': 'jira-users'}) def create_sprint_stories(self, board_id, sprint_id, user_stories): """Create user stories in a board and link them to a sprint. A json array with user story key, is_closed and status info is returned. User stories subtasks are also added to the story. User story tasks finished date and current taiga status are added as comments. User story finished date and current taiga status are added as comments. """ project_id = self._get_board_project_id(board_id) self.logger.info( "Creating user stories in project {} - sprint {}".format( project_id, sprint_id)) created_stories = [] for user_story in user_stories: self.logger.info("Creating user story {}".format( user_story.get("subject"))) created_story = self._create_user_story( project_id, user_story.get("subject"), user_story.get("description"), user_story.get("tags"), user_story.get("total_points")) self.logger.info("Adding user story {} to sprint {}".format( created_story.key, sprint_id)) self.jira.add_issues_to_sprint(sprint_id, [created_story.key]) for task in user_story.get("tasks"): created_task = self._create_user_story_task( project_id, created_story.id, task.get("subject"), task.get("description"), task.get("finished_date")) # Add as comment user story finished date self._add_comment( created_task.id, "Finished date: '{}'".format(task.get('finished_date'))) self._add_comment( created_task.id, "Taiga status: '{}'".format(task.get("status"))) # Update task status self._update_task_status(created_task.key, task.get("status")) created_stories.append({ "key": created_story.key, "is_closed": user_story.get("is_closed"), "status": user_story.get("status") }) # Add as comment user story finished date self._add_comment( created_story.id, "Finished date: '{}'".format(user_story.get('finish_date'))) self._add_comment( created_story.id, "Taiga status: '{}'".format(user_story.get("status"))) return created_stories def add_backlogs_stories(self, board_id, user_stories): """Add user stories to the backlog of the board. Taiga original status and backlog order are added as comments. """ project_id = self._get_board_project_id(board_id) self.logger.info( "Creating user stories in project {} backlog".format(project_id)) created_stories = [] for user_story in user_stories: self.logger.info("Creating user story {}".format( user_story.get("subject"))) created_story = self._create_user_story( project_id, user_story.get("subject"), user_story.get("description"), user_story.get("tags"), user_story.get("total_points")) created_stories.append({"key": created_story.key}) # Add as comment user story finished date self._add_comment( created_story.id, "Taiga status: '{}'".format(user_story.get("status"))) self._add_comment( created_story.id, "Taiga backlog order: '{}'".format( user_story.get("backlog_order"))) return created_stories
def run( self, username: str = None, access_token: str = None, server_url: str = None, project_name: str = None, assignee: str = "-1", issue_type: str = None, summary: str = None, description: str = None, ) -> None: """ Run method for this Task. Invoked by calling this Task after initialization within a Flow context, or by using `Task.bind`. Args: - username(str): the jira username, provided with a Prefect secret (defaults to JIRAUSER in JIRASECRETS) - access_token (str): a Jira access token, provided with a Prefect secret (defaults to JIRATOKEN in JIRASECRETS) - server_url (str): the URL of your atlassian account e.g. "https://test.atlassian.net". Can also be set as a Prefect Secret. Defaults to the one provided at initialization - project_name(str): the key for your jira project; defaults to the one provided at initialization - assignee (str, optional): the atlassian accountId of the person you want to assign the ticket to; defaults to "automatic" if this is not set; defaults to the one provided at initialization - issue_type (str, optional): the type of issue you want to create; defaults to 'Task' - summary (str, optional): summary or title for your issue; defaults to the one provided at initialization - description (str, optional): description or additional information for the issue; defaults to the one provided at initialization Raises: - ValueError: if a `project_name` or 'summary' are not provided Returns: - None """ jira_credentials = cast(dict, Secret("JIRASECRETS").get()) if username is None: username = jira_credentials["JIRAUSER"] if access_token is None: access_token = jira_credentials["JIRATOKEN"] if server_url is None: server_url = jira_credentials["JIRASERVER"] if issue_type is None: issue_type = "Task" if project_name is None: raise ValueError("A project name must be provided") if summary is None: raise ValueError("A summary must be provided") jira = JIRA(basic_auth=(username, access_token), options={"server": server_url}) options = { "project": project_name, "assignee": { "accountId": assignee }, "issuetype": { "name": issue_type }, "summary": summary, "description": description, } created = jira.create_issue(options) if not created: raise ValueError("Creating Jira Issue failed")
versions = list() for branch in branches.split(","): versions.append({"id": versions_map[branch]}) issue_dict = { 'project': {"key": "LIT"}, 'issuetype': {"name": "Bug"}, 'components': [{"name": "LIT-BTD"}], 'assignee': {"name": the_assignee}, 'description': description, 'summary': summary, 'customfield_10500': [{"value": "Multi"}], 'versions': versions, 'labels': labels, 'customfield_10500': None, 'priority': { "name" : priority } } #print(json.dumps(issue_dict, indent=4)) print "please wait while creating the issue and uploading the file .." issue = jira.create_issue(fields=issue_dict) if attachment_name is not None: with open(attachment_name, 'rb') as the_attachment: jira.add_attachment(issue, the_attachment, attachment_name) print "" print "issue created with ID: ",issue print "" #webbrowser.open_new("https://jira.atypon.com/browse/{}".format(issue))
def submit_jira_ticket(request): username = request.user.username jira_setting = jirasetting.objects.filter(username=username) user = request.user for jira in jira_setting: jira_url = jira.jira_server username = jira.jira_username password = jira.jira_password jira_server = jira_url jira_username = signing.loads(username) jira_password = signing.loads(password) options = {'server': jira_server} try: jira_ser = JIRA(options, basic_auth=(jira_username, jira_password)) jira_projects = jira_ser.projects() except Exception as e: print(e) notify.send(user, recipient=user, verb='Jira settings not found') if request.method == 'GET': summary = request.GET['summary'] description = request.GET['description'] scanner = request.GET['scanner'] vuln_id = request.GET['vuln_id'] scan_id = request.GET['scan_id'] return render( request, 'submit_jira_ticket.html', { 'jira_projects': jira_projects, 'summary': summary, 'description': description, 'scanner': scanner, 'vuln_id': vuln_id, 'scan_id': scan_id }) if request.method == 'POST': summary = request.POST.get('summary') description = request.POST.get('description') project_id = request.POST.get('project_id') issue_type = request.POST.get('issue_type') vuln_id = request.POST.get('vuln_id') scanner = request.POST.get('scanner') scan_id = request.POST.get('scan_id') issue_dict = { 'project': { 'id': project_id }, 'summary': summary, 'description': description, 'issuetype': { 'name': issue_type }, } new_issue = jira_ser.create_issue(fields=issue_dict) # print new_issue if scanner == 'zap': zap_scan_results_db.objects.filter( username=username, vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('zapscanner:zap_vuln_details') + '?scan_id=%s&scan_name=%s' % (scan_id, summary)) elif scanner == 'burp': burp_scan_result_db.objects.filter( username=username, vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('burpscanner:burp_vuln_out') + '?scan_id=%s&scan_name=%s' % (scan_id, summary)) elif scanner == 'arachni': arachni_scan_result_db.objects.filter( username=username, vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('arachniscanner:arachni_vuln_out') + '?scan_id=%s&scan_name=%s' % (scan_id, summary)) elif scanner == 'netsparker': netsparker_scan_result_db.objects.filter( username=username, vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('netsparkerscanner:netsparker_vuln_out') + '?scan_id=%s&scan_name=%s' % (scan_id, summary)) elif scanner == 'webinspect': webinspect_scan_result_db.objects.filter( username=username, vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('webinspectscanner:webinspect_vuln_out') + '?scan_id=%s&scan_name=%s' % (scan_id, summary)) elif scanner == 'bandit': bandit_scan_results_db.objects.filter( username=username, vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('banditscanner:banditscan_vuln_data') + '?scan_id=%s&test_name=%s' % (scan_id, summary)) elif scanner == 'dependencycheck': dependencycheck_scan_results_db.objects.filter( username=username, vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('dependencycheck:dependencycheck_vuln_data') + '?scan_id=%s&test_name=%s' % (scan_id, summary)) elif scanner == 'findbugs': findbugs_scan_results_db.objects.filter( username=username, vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('findbugs:findbugs_vuln_data') + '?scan_id=%s&test_name=%s' % (scan_id, summary)) elif scanner == 'clair': clair_scan_results_db.objects.filter( username=username, vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('clair:clair_vuln_data') + '?scan_id=%s&test_name=%s' % (scan_id, summary)) elif scanner == 'open_vas': ov_scan_result_db.objects.filter( username=username, vul_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('networkscanners:vul_details') + '?scan_id=%s' % scan_id) elif scanner == 'nessus': nessus_report_db.objects.filter( username=username, vul_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect( reverse('networkscanners:nessus_vuln_details') + '?scan_id=%s' % scan_id)
def report_issue(self, build): try: jira = JIRA(server='https://issues.voltdb.com/', basic_auth=(JIRA_USER, JIRA_PASS)) except: logging.exception('Could not connect to Jira') return build_report_url = self.jhost + '/job/' + job + '/' + str(build) + '/api/python' build_report = eval(self.read_url(build_report_url)) build_url = build_report.get('url') build_result = build_report.get('result') if build_result == 'SUCCESS': # only generate Jira issue if the test fails print 'No new issue created. Build ' + str(build) + 'resulted in: ' + build_result return summary_url = self.jhost + '/job/' + job + '/' + str(build) + '/artifact/tests/sqlgrammar/summary.out' summary_report = self.read_url(summary_url) pframe_split = summary_report.split('Problematic frame:') pframe_split = pframe_split[1].split('C') pframe_split = pframe_split[1].split(']') pframe_split = pframe_split[1].split('#') pframe = pframe_split[0].strip() summary = job + ':' + str(build) + ' - ' + pframe # search_issues gets a parsing error on (), so escape it. existing = jira.search_issues('summary ~ \'%s\'' % summary.replace('()','\\\\(\\\\)',10)) if len(existing) > 0: print 'No new Jira issue created. Build ' + str(build) + ' has already been reported.' return 'Already reported' old_issue = '' existing = jira.search_issues('summary ~ \'%s\'' % pframe_split[0].strip().replace('()','\\\\(\\\\)',10)) for issue in existing: if str(issue.fields.status) != 'Closed' and u'grammar-gen' in issue.fields.labels: old_issue = issue build_artifacts = build_report.get('artifacts')[0] pid_fileName = build_artifacts['fileName'] pid_url = build_url + 'artifact/' + pid_fileName query_split = summary_report.split('(or it was never started??), after SQL statement:') crash_query = query_split[1] hash_split = summary_report.split('#', 1) hash_split = hash_split[1].split('# See problematic frame for where to report the bug.') sigsegv_message = hash_split[0] + '# See problematic frame for where to report the bug.\n#' description = job + ' build ' + str(build) + ' : ' + str(build_result) + '\n' \ + 'Jenkins build: ' + build_url + ' \n \n' \ + 'DDL: ' + 'https://github.com/VoltDB/voltdb/blob/master/tests/sqlgrammar/DDL.sql' + ' \n \n' \ + 'hs_err_pid: ' + pid_url + ' \n \n' \ + 'SIGSEGV Message: \n' + '#' + sigsegv_message + ' \n \n' \ + 'Query that Caused the Crash: ' + crash_query description = description.replace('#', '\#') labels = ['grammar-gen'] component = 'Core' components = jira.project_components(JIRA_PROJECT) jira_component = {} for c in components: if c.name == component: jira_component = { 'name': c.name, 'id': c.id } break current_version_raw = str(self.read_url('https://raw.githubusercontent.com/VoltDB/voltdb/master/version.txt')) current_version_float = float(current_version_raw) current_version = 'V' + current_version_raw current_version = current_version.strip() next_version = current_version_float + .1 next_version = str(next_version) next_version = 'V' + next_version next_version = next_version[:4] jira_versions = jira.project_versions(JIRA_PROJECT) this_version = {} new_version = {} for v in jira_versions: if str(v.name) == current_version: this_version = { 'name': v.name, 'id': v.id } if str(v.name) == next_version: new_version = { 'name': v.name, 'id': v.id } issue_dict = { 'project': JIRA_PROJECT, 'summary': summary, 'description': description, 'issuetype': {'name': 'Bug'}, 'priority': {'name': 'Blocker'}, 'labels': labels, 'customfield_10430': {'value': 'CORE team'}, 'components': [jira_component] } if new_version: issue_dict['versions'] = [new_version] issue_dict['fixVersions'] = [new_version] elif this_version: issue_dict['versions'] = [this_version] issue_dict['fixVersions'] = [this_version] if old_issue: new_comment = jira.add_comment(old_issue, description) print 'JIRA-action: New comment on issue: ' + str(old_issue) + ' created for failure on build ' + str(build) else: new_issue = jira.create_issue(fields=issue_dict) print 'JIRA-action: New issue ' + new_issue.key + ' created for failure on build ' + str(build)
def main(): context = utils.collect_context() tmpl_host_summary = 'Check_MK: $HOSTNAME$ - $HOSTSHORTSTATE$' tmpl_service_summary = 'Check_MK: $HOSTNAME$/$SERVICEDESC$ $SERVICESHORTSTATE$' tmpl_label = 'monitoring' for necessary in [ 'PARAMETER_URL', 'PARAMETER_USERNAME', 'PARAMETER_PASSWORD', 'PARAMETER_HOST_CUSTOMID', 'PARAMETER_SERVICE_CUSTOMID' ]: if necessary not in context: sys.stderr.write("%s not set" % necessary) return 2 if "PARAMETER_IGNORE_SSL" in context: sys.stdout.write( "Unverified HTTPS request warnings are ignored. Use with caution.\n" ) jira = JIRA(server=context['PARAMETER_URL'], basic_auth=(context['PARAMETER_USERNAME'], context['PARAMETER_PASSWORD']), options={'verify': False}) else: jira = JIRA(server=context['PARAMETER_URL'], basic_auth=(context['PARAMETER_USERNAME'], context['PARAMETER_PASSWORD'])) if context['WHAT'] == 'HOST': summary = context.get('PARAMETER_HOST_SUMMARY') or tmpl_host_summary svc_desc = context['HOSTOUTPUT'] custom_field = int(context['PARAMETER_HOST_CUSTOMID']) custom_field_value = int(context['HOSTPROBLEMID']) else: summary = context.get( 'PARAMETER_SERVICE_SUMMARY') or tmpl_service_summary svc_desc = context['SERVICEOUTPUT'] custom_field = int(context['PARAMETER_SERVICE_CUSTOMID']) custom_field_value = int(context['SERVICEPROBLEMID']) context['SUBJECT'] = utils.substitute_context(summary, context) label = context.get('PARAMETER_LABEL') or tmpl_label newissue = { u'labels': [label], u'summary': context['SUBJECT'], u'description': svc_desc, } if 'PARAMETER_PROJECT' in context: newissue[u'project'] = {u'id': context['PARAMETER_PROJECT']} if 'CONTACT_JIRAPROJECT' in context: newissue[u'project'] = {u'id': context['CONTACT_JIRAPROJECT']} if 'PARAMETER_ISSUETYPE' in context: newissue[u'issuetype'] = {u'id': context['PARAMETER_ISSUETYPE']} if 'CONTACT_JIRAISSUETYPE' in context: newissue[u'issuetype'] = {u'id': context['CONTACT_JIRAISSUETYPE']} if 'PARAMETER_PRIORITY' in context: newissue[u'priority'] = {u'id': context['PARAMETER_PRIORITY']} if 'CONTACT_JIRAPRIORITY' in context: newissue[u'priority'] = {u'id': context['CONTACT_JIRAPRIORITY']} if 'project' not in newissue: sys.stderr.write("No JIRA project ID set, discarding notification") return 2 if 'issuetype' not in newissue: sys.stderr.write("No JIRA issue type ID set") return 2 try: custom_field_exists = jira.search_issues( "cf[%d]=%d" % (custom_field, custom_field_value)) except JIRAError as err: sys.stderr.write( 'Unable to query custom field search, JIRA response code %s, %s' % (err.status_code, err.text)) return 2 if not custom_field_exists: newissue[u'customfield_%d' % custom_field] = custom_field_value if context['NOTIFICATIONTYPE'] == 'PROBLEM': try: issue = jira.create_issue(fields=newissue) except JIRAError as err: sys.stderr.write( 'Unable to create issue, JIRA response code %s, %s' % (err.status_code, err.text)) return 2 sys.stdout.write('Created %s\n' % issue.permalink()) if 'PARAMETER_MONITORING' in context: if context['PARAMETER_MONITORING'].endswith('/'): # remove trailing slash context['PARAMETER_MONITORING'] = context[ 'PARAMETER_MONITORING'][:-1] if context['WHAT'] == 'SERVICE': url = context['PARAMETER_MONITORING'] + context['SERVICEURL'] else: url = context['PARAMETER_MONITORING'] + context['HOSTURL'] try: rl = jira.add_simple_link(issue, { 'url': url, 'title': 'Monitoring' }) except JIRAError as err: sys.stderr.write( 'Unable to create link in issue, JIRA response code %s, %s\n' % (err.status_code, err.text)) return 2 sys.stdout.write('Created JIRA simple link: %s' % rl) if context['NOTIFICATIONTYPE'] == 'RECOVERY' and custom_field_exists: if "PARAMETER_RESOLUTION" not in context: sys.stderr.write( "Ticket resolution not enabled in wato rule. Don't send a resolution to jira\n" ) return 0 else: resolution = None if 'PARAMETER_RESOLUTION' in context: resolution = context['PARAMETER_RESOLUTION'] if 'CONTACT_JIRARESOLUTION' in context: resolution = context['CONTACT_JIRARESOLUTION'] if resolution is None: sys.stderr.write("No JIRA resolution ID set") return 2 for issue in custom_field_exists: try: jira.transition_issue(issue, resolution, comment=newissue['description']) sys.stdout.write('Resolved %s' % issue.permalink()) except JIRAError as err: sys.stderr.write( 'Unable to resolve %s, JIRA response code %s, %s' % (issue.permalink(), err.status_code, err.text)) return 2
# Developed by Koushik - Apr 2020 # Purpose: Create a JIRA issue using dictionary from jira import JIRA import getpass # login to JIRA using username and password print("Enter credentials to login to JIRA:") user = input("Username: ") pw = getpass.getpass() jira = JIRA(auth=(user, pw), options={'server': 'https://jira.kfplc.com'}) # ---- Create JIRA issue using dictionary jira_dict_convert = { 'project': { 'key': 'DRRR' }, 'summary': 'Test Bug title using Dictionary - JIRA Python', 'issuetype': { 'name': 'Bug' }, 'priority': { 'name': 'High' }, 'description': 'Test story description using Dict - JIRA Python' } newID = jira.create_issue(jira_dict_convert) print('\nNew issue created using dictionary: ', newID)
def report_issue(self, build): try: jira = JIRA(server='https://issues.voltdb.com/', basic_auth=(JIRA_USER, JIRA_PASS)) except: logging.exception('Could not connect to Jira') return build_report_url = self.jhost + '/job/' + job + '/' + str( build) + '/api/python' build_report = eval(self.read_url(build_report_url)) build_url = build_report.get('url') build_result = build_report.get('result') if build_result == 'SUCCESS': # only generate Jira issue if the test fails print 'No new issue created. Build ' + str( build) + 'resulted in: ' + build_result return summary_url = self.jhost + '/job/' + job + '/' + str( build) + '/artifact/tests/sqlgrammar/summary.out' summary_report = self.read_url(summary_url) pframe_split = summary_report.split('Problematic frame:') pframe_split = pframe_split[1].split('C') pframe_split = pframe_split[1].split(']') pframe_split = pframe_split[1].split('#') pframe = pframe_split[0].strip() summary = job + ':' + str(build) + ' - ' + pframe # search_issues gets a parsing error on (), so escape it. existing = jira.search_issues('summary ~ \'%s\'' % summary.replace('()', '\\\\(\\\\)', 10)) if len(existing) > 0: print 'No new Jira issue created. Build ' + str( build) + ' has already been reported.' return 'Already reported' old_issue = '' existing = jira.search_issues( 'summary ~ \'%s\'' % pframe_split[0].strip().replace('()', '\\\\(\\\\)', 10)) for issue in existing: if str(issue.fields.status ) != 'Closed' and u'grammar-gen' in issue.fields.labels: old_issue = issue build_artifacts = build_report.get('artifacts')[0] pid_fileName = build_artifacts['fileName'] pid_url = build_url + 'artifact/' + pid_fileName query_split = summary_report.split( '(or it was never started??), after SQL statement:') crash_query = query_split[1] hash_split = summary_report.split('#', 1) hash_split = hash_split[1].split( '# See problematic frame for where to report the bug.') sigsegv_message = hash_split[ 0] + '# See problematic frame for where to report the bug.\n#' description = job + ' build ' + str(build) + ' : ' + str(build_result) + '\n' \ + 'Jenkins build: ' + build_url + ' \n \n' \ + 'DDL: ' + 'https://github.com/VoltDB/voltdb/blob/master/tests/sqlgrammar/DDL.sql' + ' \n \n' \ + 'hs_err_pid: ' + pid_url + ' \n \n' \ + 'SIGSEGV Message: \n' + '#' + sigsegv_message + ' \n \n' \ + 'Query that Caused the Crash: ' + crash_query description = description.replace('#', '\#') labels = ['grammar-gen'] component = 'Core' components = jira.project_components(JIRA_PROJECT) jira_component = {} for c in components: if c.name == component: jira_component = {'name': c.name, 'id': c.id} break current_version_raw = str( self.read_url( 'https://raw.githubusercontent.com/VoltDB/voltdb/master/version.txt' )) current_version_float = float(current_version_raw) current_version = 'V' + current_version_raw current_version = current_version.strip() next_version = current_version_float + .1 next_version = str(next_version) next_version = 'V' + next_version next_version = next_version[:4] jira_versions = jira.project_versions(JIRA_PROJECT) this_version = {} new_version = {} for v in jira_versions: if str(v.name) == current_version: this_version = {'name': v.name, 'id': v.id} if str(v.name) == next_version: new_version = {'name': v.name, 'id': v.id} issue_dict = { 'project': JIRA_PROJECT, 'summary': summary, 'description': description, 'issuetype': { 'name': 'Bug' }, 'priority': { 'name': 'Blocker' }, 'labels': labels, 'customfield_10430': { 'value': 'CORE team' }, 'components': [jira_component] } if new_version: issue_dict['versions'] = [new_version] issue_dict['fixVersions'] = [new_version] elif this_version: issue_dict['versions'] = [this_version] issue_dict['fixVersions'] = [this_version] if old_issue: new_comment = jira.add_comment(old_issue, description) print 'JIRA-action: New comment on issue: ' + str( old_issue) + ' created for failure on build ' + str(build) else: new_issue = jira.create_issue(fields=issue_dict) print 'JIRA-action: New issue ' + new_issue.key + ' created for failure on build ' + str( build)
def create_bug_issue(self, channel, summary, description, component, version, labels, user=JIRA_USER, passwd=JIRA_PASS, project=JIRA_PROJECT): """ Creates a bug issue on Jira :param channel: The channel to notify :param summary: The title summary :param description: Description field :param component: Component bug affects :param version: Version this bug affects :param labels: Labels to attach to the issue :param user: User to report bug as :param passwd: Password :param project: Jira project """ if user and passwd and project: try: jira = JIRA(server='https://issues.voltdb.com/', basic_auth=(user, passwd)) except: self.logger.exception('Could not connect to Jira') return else: self.logger.error('Did not provide either a Jira user, a Jira password or a Jira project') return # Check for existing bug with same summary existing = jira.search_issues('summary ~ \'%s\'' % summary) if len(existing) > 0: # Already reported self.logger.info('OLD: Already reported issue with summary "' + summary + '"') return issue_dict = { 'project': project, 'summary': summary, 'description': description, 'issuetype': { 'name': 'Bug' }, 'labels': labels } jira_component = None components = jira.project_components(project) for c in components: if c.name == component: jira_component = { 'name': c.name, 'id': c.id } break if jira_component: issue_dict['components'] = [jira_component] else: # Components is still a required field issue_dict['components'] = ['Core'] jira_version = None versions = jira.project_versions(project) version = 'V' + version for v in versions: if str(v.name) == version.strip(): jira_version = { 'name': v.name, 'id': v.id } break if jira_version: issue_dict['versions'] = [jira_version] else: # Versions is still a required field issue_dict['versions'] = ['DEPLOY-Integration'] issue_dict['fixVersions'] = [{'name':'Backlog'}] issue_dict['priority'] = {'name': 'Blocker'} new_issue = jira.create_issue(fields=issue_dict) self.logger.info('NEW: Reported issue with summary "' + summary + '"') if self.connect_to_slack(): self.post_message(channel, 'Opened issue at https://issues.voltdb.com/browse/' + new_issue.key)
class JiraIntegrator: def __init__(self, user, api_token, host): options = {"server": host} self.jira_connection = JIRA(options, basic_auth=(user, api_token)) self.event_handlers = { enums.GitHubAction.OPENED.value: self.create_new_jira_issue, enums.GitHubAction.CREATED.value: self.create_new_jira_issue, enums.GitHubAction.EDITED.value: self.update_jira_issue, enums.GitHubAction.LABELED.value: self.update_jira_label, enums.GitHubAction.UNLABELED.value: self.update_jira_label, enums.GitHubAction.CLOSED.value: self.close_jira_issue, enums.GitHubAction.REOPENED.value: self.reopen_jira_issue, } def handle(self, event): status_code = 500 try: handler = self.event_handlers[event["action"]] handler(event["issue"], event["repository"]) status_code = 200 except (KeyError, ValueError) as err: status_code = 404 logging.exception(f"Can not find: {repr(err)}") except Exception as err: logging.exception(f"Error occurs: {repr(err)}") return { "statusCode": status_code, "headers": {"Access-Control-Allow-Origin": "*"}, } def create_new_jira_issue(self, issue, repository): issuetype = {"name": utils.get_issue_type(issue["labels"])} github_link_field_name = self._get_field_id("GitHub Link") github_author_field_name = self._get_field_id("GitHub Author") github_link = utils.find_github_link(repository["full_name"], issue["number"]) labels = utils.get_jira_labels(issue["labels"]) project = utils.find_project(repository) fields = { "project": project, "summary": issue["title"], "description": issue["body"], "issuetype": issuetype, "labels": labels, github_link_field_name: github_link, github_author_field_name: issue.get("user", {}).get("login"), } component = None try: if "mirumee/saleor-dashboard" in github_link: component = self.jira_connection.component(COMPONENT_ID_DASHBOARD) if "mirumee/saleor-docs" in github_link: component = self.jira_connection.component(COMPONENT_ID_DOCS) except Exception as err: logging.exception(f"Failed to assign component: {repr(err)}") if component: fields["components"] = [{"name": component.name}] self.jira_connection.create_issue(**fields) def update_jira_issue(self, issue, repository): jira_issue = self._find_jira_issue(repository["full_name"], issue["number"]) fields = {"summary": issue["title"], "description": issue["body"]} jira_issue.update(fields=fields) def update_jira_label(self, issue, repository): jira_issue = self._find_jira_issue(repository["full_name"], issue["number"]) issuetype = {"name": utils.get_issue_type(issue["labels"])} labels = utils.get_jira_labels(issue["labels"]) fields = {"issuetype": issuetype, "labels": labels} jira_issue.update(fields=fields) def close_jira_issue(self, issue, repository): jira_issue = self._find_jira_issue(repository["full_name"], issue["number"]) ready_to_test_transition = self._get_ready_to_test_transition(jira_issue) self.jira_connection.transition_issue( jira_issue, ready_to_test_transition["id"] ) def reopen_jira_issue(self, issue, repository): jira_issue = self._find_jira_issue(repository["full_name"], issue["number"]) to_do_transition = self._get_to_do_transition(jira_issue) self.jira_connection.transition_issue(jira_issue, to_do_transition["id"]) def _get_field_id(self, field_name): fields = self.jira_connection.fields() selected_field = filter( lambda field: field["name"].lower() == field_name.lower(), fields ) return next(selected_field)["key"] def _find_jira_issue(self, github_project, issue_number): query = '"GitHub Link" = "{github_link}"'.format( github_link=utils.find_github_link(github_project, issue_number) ) issues = self.jira_connection.search_issues(query) if not issues: raise ValueError(f"Can not find jira issue with number {issue_number}") return issues[0] def _get_to_do_transition(self, issue): return self._get_transition_by_name(issue, enums.JiraTransition.TO_DO.value) def _get_ready_to_test_transition(self, issue): return self._get_transition_by_name( issue, enums.JiraTransition.READY_TO_TEST.value ) def _get_transition_by_name(self, issue, transition): transitions = self.jira_connection.transitions(issue) ready_to_test_transition = next( filter(lambda tr: tr["name"].lower() == transition, transitions) ) return ready_to_test_transition
class JiraWrapper: JIRA_REQUEST = 'project={} AND labels in ({})' def __init__(self, url, user, password, jira_project, assignee, issue_type='Bug', labels=None, watchers=None, jira_epic_key=None): self.valid = True self.url = url self.password = password self.user = user try: self.connect() except: self.valid = False return self.projects = [project.key for project in self.client.projects()] self.project = jira_project if self.project not in self.projects: self.client.close() self.valid = False return self.assignee = assignee self.issue_type = issue_type self.labels = list() if labels: self.labels = [label.strip() for label in labels.split(",")] self.watchers = list() if watchers: self.watchers = [ watcher.strip() for watcher in watchers.split(",") ] self.jira_epic_key = jira_epic_key self.client.close() def connect(self): self.client = JIRA(self.url, basic_auth=(self.user, self.password)) def markdown_to_jira_markdown(self, content): return content.replace("###", "h3.").replace("**", "*") def create_issue(self, title, priority, description, issue_hash, attachments=None, get_or_create=True, additional_labels=None): description = self.markdown_to_jira_markdown(description) _labels = [issue_hash] if additional_labels and isinstance(additional_labels, list): _labels.extend(additional_labels) _labels.extend(self.labels) issue_data = { 'project': { 'key': self.project }, 'summary': re.sub('[^A-Za-z0-9//\. _]+', '', title), 'description': description, 'issuetype': { 'name': self.issue_type }, 'assignee': { 'name': self.assignee }, 'priority': { 'name': priority }, 'labels': _labels } jira_request = self.JIRA_REQUEST.format(issue_data["project"]["key"], issue_hash) if get_or_create: issue, created = self.get_or_create_issue(jira_request, issue_data) else: issue = self.post_issue(issue_data) created = True if attachments: for attachment in attachments: if 'binary_content' in attachment: self.add_attachment( issue.key, attachment=attachment['binary_content'], filename=attachment['message']) for watcher in self.watchers: self.client.add_watcher(issue.id, watcher) if self.jira_epic_key: self.client.add_issues_to_epic(self.jira_epic_key, [issue.id]) return issue, created def add_attachment(self, issue_key, attachment, filename=None): issue = self.client.issue(issue_key) for _ in issue.fields.attachment: if _.filename == filename: return self.client.add_attachment(issue, attachment, filename) def post_issue(self, issue_data): print(issue_data) issue = self.client.create_issue(fields=issue_data) return issue def get_or_create_issue(self, search_string, issue_data): issuetype = issue_data['issuetype']['name'] created = False jira_results = self.client.search_issues(search_string) issues = [] for each in jira_results: if each.fields.summary == issue_data.get('summary', None): issues.append(each) if len(issues) == 1: issue = issues[0] if len(issues) > 1: print(' more then 1 issue with the same summary') else: print(issuetype + 'issue already exists:' + issue.key) else: issue = self.post_issue(issue_data) created = True return issue, created
flag=0 print("Flag initiated to 0") if "Recovery" in sys.argv[2]: print("1") extractedSubject=sys.argv[2][10:] for issue in issues: print("2") print(issue.fields.summary) print(extractedSubject) if issue.fields.summary==extractedSubject: if "To Do" == str(issue.fields.status): jira.transition_issue(issue, '61') jira.add_comment(issue, sys.argv[3]) else: print('3') for issue in issues: if issue.fields.summary==sys.argv[2]: print("4") if "Resolved For Now" == str(issue.fields.status): print("5") jira.transition_issue(issue, '81') if "Closed" != str(issue.fields.status): print("6") flag=1 jira.add_comment(issue, sys.argv[3]) if(flag==0): print("Inside flag=0") new_issue = jira.create_issue(project='PCAB', summary=sys.argv[2],description=sys.argv[3], issuetype={'name': 'Bug'}) #jira.create_issue_link('Duplicate',new_issue,parent_issue,None)
def update_or_create_jira_issue(study_id, user_token, is_curator): try: params = app.config.get('JIRA_PARAMS') user_name = params['username'] password = params['password'] updated_studies = [] try: jira = JIRA(options=options, basic_auth=(user_name, password)) except: return False, 'Could not connect to JIRA server, incorrect username or password?', updated_studies # Get the MetaboLights project mtbls_project = jira.project(project) studies = [study_id] if not study_id and is_curator: studies = get_all_studies(user_token) for study in studies: study_id = study[0] user_name = study[1] release_date = study[2] update_date = study[3] study_status = study[4] curator = study[5] issue = [] summary = None # Get an issue based on a study accession search pattern search_param = "project='" + mtbls_project.key + "' AND summary ~ '" + study_id + " \\\-\\\ 20*'" issues = jira.search_issues(search_param) # project = MetaboLights AND summary ~ 'MTBLS121 ' new_summary = study_id + ' - ' + release_date.replace('-', '') + ' - ' + \ study_status + ' (' + user_name + ')' try: if issues: issue = issues[0] else: if study_status == 'Submitted': logger.info("Could not find Jira issue for " + search_param) print("Creating new Jira issue for " + search_param) issue = jira.create_issue(project=mtbls_project.key, summary='MTBLS study - To be updated', description='Created by API', issuetype={'name': 'Story'}) else: continue # Only create new cases if the study is in status Submitted except Exception: # We could not find or create a Jira issue continue summary = issue.fields.summary # Follow pattern 'MTBLS123 - YYYYMMDD - Status' try: assignee = issue.fields.assignee.name except: assignee = "" valid_curator = False jira_curator = "" if curator: if curator.lower() == 'mark': jira_curator = 'mwilliam' valid_curator = True elif curator.lower() == 'keeva': jira_curator = 'keeva' valid_curator = True else: jira_curator = "" # Release date or status has changed, or the assignee (curator) has changed if summary.startswith('MTBLS') and (summary != new_summary or assignee != jira_curator): # Add "Curation" Epic issues_to_add = [issue.key] jira.add_issues_to_epic(curation_epic, issues_to_add) # Add the Curation Epic labels = maintain_jira_labels(issue, study_status, user_name) # Add a comment to the issue. comment_text = 'Status ' + study_status + '. Database update date ' + update_date jira.add_comment(issue, comment_text) # Change the issue's summary, comments and description. issue.update(summary=new_summary, fields={"labels": labels}, notify=False) if valid_curator: # ToDo, what if the curation log is not up to date? issue.update(assignee={'name': jira_curator}) updated_studies.append(study_id) logger.info('Updated Jira case for study ' + study_id) print('Updated Jira case for study ' + study_id) except Exception: return False, 'Update failed', updated_studies return True, 'Ticket(s) updated successfully', updated_studies
class JiraClient: def __init__(self, options, basic_auth, project): self.jira = JIRA(options, basic_auth=basic_auth) self.project = project def get_issues_by_summary(self, summary): """ Find issues by using the summary (issue title) Args: summary Return: A list of issues """ try: issues = self.jira.search_issues("project={0} AND summary ~ '{1}'".format(self.project, summary)) except Exception: raise return issues def get_issue_by_key(self, key): """ Find issue by using the key (e.g BEAM-1234) Args: key Return: issue """ try: issue = self.jira.issue(key) except Exception: raise return issue def create_issue(self, summary, components, description, issuetype='Bug', assignee=None, parent_key=None): """ Create a new issue Args: summary - Issue title components - A list of components description (optional) - A string that describes the issue issuetype (optional) - Bug, Improvement, New Feature, Sub-task, Task, Wish, etc. assignee (optional) - A string of JIRA user name parent_key (optional) - The parent issue key is required when creating a subtask. Return: Issue created """ fields = { 'project': {'key': self.project}, 'summary': summary, 'description': description, 'issuetype': {'name': issuetype}, 'components': [], } for component in components: fields['components'].append({'name': component}) if assignee is not None: fields['assignee'] = {'name': assignee} if parent_key is not None: fields['parent'] = {'key': parent_key} fields['issuetype'] = {'name': 'Sub-task'} try: new_issue = self.jira.create_issue(fields = fields) except Exception: raise return new_issue def update_issue(self, issue, summary=None, components=None, description=None, assignee=None, notify=True): """ Create a new issue Args: issue - Jira issue object summary (optional) - Issue title components (optional) - A list of components description (optional) - A string that describes the issue assignee (optional) - A string of JIRA user name notify - Query parameter notifyUsers. If true send the email with notification that the issue was updated to users that watch it. Admin or project admin permissions are required to disable the notification. Return: Issue created """ fields={} if summary: fields['summary'] = summary if description: fields['description'] = description if assignee: fields['assignee'] = {'name': assignee} if components: fields['components'] = [] for component in components: fields['components'].append({'name': component}) try: issue.update(fields=fields, notify=notify) except Exception: raise def reopen_issue(self, issue): """ Reopen an issue Args: issue - Jira issue object """ try: self.jira.transition_issue(issue.key, 3) except: raise
class JiraAPI(object): def __init__(self, hostname=None, username=None, password=None, path="", debug=False, clean_obsolete=True, max_time_window=12): self.logger = logging.getLogger('JiraAPI') if debug: self.logger.setLevel(logging.DEBUG) if "https://" not in hostname: hostname = "https://{}".format(hostname) self.username = username self.password = password self.jira = JIRA(options={'server': hostname}, basic_auth=(self.username, self.password)) self.logger.info("Created vjira service for {}".format(hostname)) self.all_tickets = [] self.JIRA_REOPEN_ISSUE = "Reopen Issue" self.JIRA_CLOSE_ISSUE = "Close Issue" self.max_time_tracking = max_time_window #in months #<JIRA Resolution: name=u'Obsolete', id=u'11'> self.JIRA_RESOLUTION_OBSOLETE = "Obsolete" self.JIRA_RESOLUTION_FIXED = "Fixed" self.clean_obsolete = clean_obsolete self.template_path = 'vulnwhisp/reporting/resources/ticket.tpl' if path: self.download_tickets(path) else: self.logger.warn("No local path specified, skipping Jira ticket download.") def create_ticket(self, title, desc, project="IS", components=[], tags=[]): labels = ['vulnerability_management'] for tag in tags: labels.append(str(tag)) self.logger.info("creating ticket for project {} title[20] {}".format(project, title[:20])) self.logger.info("project {} has a component requirement: {}".format(project, self.PROJECT_COMPONENT_TABLE[project])) project_obj = self.jira.project(project) components_ticket = [] for component in components: exists = False for c in project_obj.components: if component == c.name: self.logger.debug("resolved component name {} to id {}".format(c.name, c.id)) components_ticket.append({ "id": c.id }) exists=True if not exists: self.logger.error("Error creating Ticket: component {} not found".format(component)) return 0 new_issue = self.jira.create_issue(project=project, summary=title, description=desc, issuetype={'name': 'Bug'}, labels=labels, components=components_ticket) self.logger.info("Ticket {} created successfully".format(new_issue)) return new_issue #Basic JIRA Metrics def metrics_open_tickets(self, project=None): jql = "labels= vulnerability_management and resolution = Unresolved" if project: jql += " and (project='{}')".format(project) self.logger.debug('Executing: {}'.format(jql)) return len(self.jira.search_issues(jql, maxResults=0)) def metrics_closed_tickets(self, project=None): jql = "labels= vulnerability_management and NOT resolution = Unresolved AND created >=startOfMonth(-{})".format(self.max_time_tracking) if project: jql += " and (project='{}')".format(project) return len(self.jira.search_issues(jql, maxResults=0)) def sync(self, vulnerabilities, project, components=[]): #JIRA structure of each vulnerability: [source, scan_name, title, diagnosis, consequence, solution, ips, risk, references] self.logger.info("JIRA Sync started") # [HIGIENE] close tickets older than 12 months as obsolete # Higiene clean up affects to all tickets created by the module, filters by label 'vulnerability_management' if self.clean_obsolete: self.close_obsolete_tickets() for vuln in vulnerabilities: # JIRA doesn't allow labels with spaces, so making sure that the scan_name doesn't have spaces # if it has, they will be replaced by "_" if " " in vuln['scan_name']: vuln['scan_name'] = "_".join(vuln['scan_name'].split(" ")) exists = False to_update = False ticketid = "" ticket_assets = [] exists, to_update, ticketid, ticket_assets = self.check_vuln_already_exists(vuln) if exists: # If ticket "resolved" -> reopen, as vulnerability is still existent self.reopen_ticket(ticketid) self.add_label(ticketid, vuln['risk']) continue elif to_update: self.ticket_update_assets(vuln, ticketid, ticket_assets) self.add_label(ticketid, vuln['risk']) continue try: tpl = template(self.template_path, vuln) except Exception as e: self.logger.error('Exception templating: {}'.format(str(e))) return 0 self.create_ticket(title=vuln['title'], desc=tpl, project=project, components=components, tags=[vuln['source'], vuln['scan_name'], 'vulnerability', vuln['risk']]) self.close_fixed_tickets(vulnerabilities) # we reinitialize so the next sync redoes the query with their specific variables self.all_tickets = [] return True def check_vuln_already_exists(self, vuln): # we need to return if the vulnerability has already been reported and the ID of the ticket for further processing #function returns array [duplicated(bool), update(bool), ticketid, ticket_assets] title = vuln['title'] labels = [vuln['source'], vuln['scan_name'], 'vulnerability_management', 'vulnerability'] #list(set()) to remove duplicates assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", ",".join(vuln['ips'])))) if not self.all_tickets: self.logger.info("Retrieving all JIRA tickets with the following tags {}".format(labels)) # we want to check all JIRA tickets, to include tickets moved to other queues # will exclude tickets older than 12 months, old tickets will get closed for higiene and recreated if still vulnerable jql = "{} AND NOT labels=advisory AND created >=startOfMonth(-{})".format(" AND ".join(["labels={}".format(label) for label in labels]), self.max_time_tracking) self.all_tickets = self.jira.search_issues(jql, maxResults=0) #WARNING: function IGNORES DUPLICATES, after finding a "duplicate" will just return it exists #it wont iterate over the rest of tickets looking for other possible duplicates/similar issues self.logger.info("Comparing Vulnerabilities to created tickets") for index in range(len(self.all_tickets)-1): checking_ticketid, checking_title, checking_assets = self.ticket_get_unique_fields(self.all_tickets[index]) if title == checking_title: difference = list(set(assets).symmetric_difference(checking_assets)) #to check intersection - set(assets) & set(checking_assets) if difference: self.logger.info("Asset mismatch, ticket to update. Ticket ID: {}".format(checking_ticketid)) return False, True, checking_ticketid, checking_assets #this will automatically validate else: self.logger.info("Confirmed duplicated. TickedID: {}".format(checking_ticketid)) return True, False, checking_ticketid, [] #this will automatically validate return False, False, "", [] def ticket_get_unique_fields(self, ticket): title = ticket.raw.get('fields', {}).get('summary').encode("ascii").strip() ticketid = ticket.key.encode("ascii") try: affected_assets_section = ticket.raw.get('fields', {}).get('description').encode("ascii").split("{panel:title=Affected Assets}")[1].split("{panel}")[0] assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", affected_assets_section))) except: self.logger.error("Ticket IPs regex failed. Ticket ID: {}".format(ticketid)) assets = [] return ticketid, title, assets def get_ticket_reported_assets(self, ticket): #[METRICS] return a list with all the affected assets for that vulnerability (including already resolved ones) return list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b",str(self.jira.issue(ticket).raw)))) def get_resolution_time(self, ticket): #get time a ticket took to be resolved ticket_obj = self.jira.issue(ticket) if self.is_ticket_resolved(ticket_obj): ticket_data = ticket_obj.raw.get('fields') #dates follow format '2018-11-06T10:36:13.849+0100' created = [int(x) for x in ticket_data['created'].split('.')[0].replace('T', '-').replace(':','-').split('-')] resolved =[int(x) for x in ticket_data['resolutiondate'].split('.')[0].replace('T', '-').replace(':','-').split('-')] start = datetime(created[0],created[1],created[2],created[3],created[4],created[5]) end = datetime(resolved[0],resolved[1],resolved[2],resolved[3],resolved[4],resolved[5]) return (end-start).days else: self.logger.error("Ticket {ticket} is not resolved, can't calculate resolution time".format(ticket=ticket)) return False def ticket_update_assets(self, vuln, ticketid, ticket_assets): # correct description will always be in the vulnerability to report, only needed to update description to new one self.logger.info("Ticket {} exists, UPDATE requested".format(ticketid)) if self.is_ticket_resolved(self.jira.issue(ticketid)): self.reopen_ticket(ticketid) try: tpl = template(self.template_path, vuln) except Exception as e: self.logger.error('Exception updating assets: {}'.format(str(e))) return 0 ticket_obj = self.jira.issue(ticketid) ticket_obj.update() assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", ",".join(vuln['ips'])))) difference = list(set(assets).symmetric_difference(ticket_assets)) comment = '' #put a comment with the assets that have been added/removed for asset in difference: if asset in assets: comment += "Asset {} have been added to the ticket as vulnerability *has been newly detected*.\n".format(asset) elif asset in ticket_assets: comment += "Asset {} have been removed from the ticket as vulnerability *has been resolved*.\n".format(asset) try: ticket_obj.update(description=tpl, comment=comment, fields={"labels":ticket_obj.fields.labels}) self.logger.info("Ticket {} updated successfully".format(ticketid)) self.add_label(ticketid, 'updated') except: self.logger.error("Error while trying up update ticket {}".format(ticketid)) return 0 def add_label(self, ticketid, label): ticket_obj = self.jira.issue(ticketid) if label not in ticket_obj.fields.labels: ticket_obj.fields.labels.append(label) try: ticket_obj.update(fields={"labels":ticket_obj.fields.labels}) self.logger.info("Added label {label} to ticket {ticket}".format(label=label, ticket=ticketid)) except: self.logger.error("Error while trying to add label {label} to ticket {ticket}".format(label=label, ticket=ticketid)) return 0 def close_fixed_tickets(self, vulnerabilities): # close tickets which vulnerabilities have been resolved and are still open found_vulns = [] for vuln in vulnerabilities: found_vulns.append(vuln['title']) comment = '''This ticket is being closed as it appears that the vulnerability no longer exists. If the vulnerability reappears, a new ticket will be opened.''' for ticket in self.all_tickets: if ticket.raw['fields']['summary'].strip() in found_vulns: self.logger.info("Ticket {} is still vulnerable".format(ticket)) continue self.logger.info("Ticket {} is no longer vulnerable".format(ticket)) self.close_ticket(ticket, self.JIRA_RESOLUTION_FIXED, comment) return 0 def is_ticket_reopenable(self, ticket_obj): transitions = self.jira.transitions(ticket_obj) for transition in transitions: if transition.get('name') == self.JIRA_REOPEN_ISSUE: self.logger.debug("Ticket is reopenable") return True self.logger.warn("Ticket can't be opened. Check Jira transitions.") return False def is_ticket_closeable(self, ticket_obj): transitions = self.jira.transitions(ticket_obj) for transition in transitions: if transition.get('name') == self.JIRA_CLOSE_ISSUE: return True self.logger.warn("Ticket can't closed. Check Jira transitions.") return False def is_ticket_resolved(self, ticket_obj): #Checks if a ticket is resolved or not if ticket_obj is not None: if ticket_obj.raw['fields'].get('resolution') is not None: if ticket_obj.raw['fields'].get('resolution').get('name') != 'Unresolved': self.logger.debug("Checked ticket {} is already closed".format(ticket_obj)) self.logger.info("Ticket {} is closed".format(ticket_obj)) return True self.logger.debug("Checked ticket {} is already open".format(ticket_obj)) return False def is_risk_accepted(self, ticket_obj): if ticket_obj is not None: if ticket_obj.raw['fields'].get('labels') is not None: labels = ticket_obj.raw['fields'].get('labels') if "risk_accepted" in labels: self.logger.warn("Ticket {} accepted risk, will be ignored".format(ticket_obj)) return True elif "server_decomission" in labels: self.logger.warn("Ticket {} server decomissioned, will be ignored".format(ticket_obj)) return True self.logger.info("Ticket {} risk has not been accepted".format(ticket_obj)) return False def reopen_ticket(self, ticketid): self.logger.debug("Ticket {} exists, REOPEN requested".format(ticketid)) # this will reopen a ticket by ticketid ticket_obj = self.jira.issue(ticketid) if self.is_ticket_resolved(ticket_obj): if not self.is_risk_accepted(ticket_obj): try: if self.is_ticket_reopenable(ticket_obj): comment = '''This ticket has been reopened due to the vulnerability not having been fixed (if multiple assets are affected, all need to be fixed; if the server is down, lastest known vulnerability might be the one reported). In the case of the team accepting the risk and wanting to close the ticket, please add the label "*risk_accepted*" to the ticket before closing it. If server has been decomissioned, please add the label "*server_decomission*" to the ticket before closing it. If you have further doubts, please contact the Security Team.''' error = self.jira.transition_issue(issue=ticketid, transition=self.JIRA_REOPEN_ISSUE, comment = comment) self.logger.info("Ticket {} reopened successfully".format(ticketid)) self.add_label(ticketid, 'reopened') return 1 except Exception as e: # continue with ticket data so that a new ticket is created in place of the "lost" one self.logger.error("error reopening ticket {}: {}".format(ticketid, e)) return 0 return 0 def close_ticket(self, ticketid, resolution, comment): # this will close a ticket by ticketid self.logger.debug("Ticket {} exists, CLOSE requested".format(ticketid)) ticket_obj = self.jira.issue(ticketid) if not self.is_ticket_resolved(ticket_obj): try: if self.is_ticket_closeable(ticket_obj): #need to add the label before closing the ticket self.add_label(ticketid, 'closed') error = self.jira.transition_issue(issue=ticketid, transition=self.JIRA_CLOSE_ISSUE, comment = comment, resolution = {"name": resolution }) self.logger.info("Ticket {} closed successfully".format(ticketid)) return 1 except Exception as e: # continue with ticket data so that a new ticket is created in place of the "lost" one self.logger.error("error closing ticket {}: {}".format(ticketid, e)) return 0 return 0 def close_obsolete_tickets(self): # Close tickets older than 12 months, vulnerabilities not solved will get created a new ticket self.logger.info("Closing obsolete tickets older than {} months".format(self.max_time_tracking)) jql = "labels=vulnerability_management AND created <startOfMonth(-{}) and resolution=Unresolved".format(self.max_time_tracking) tickets_to_close = self.jira.search_issues(jql, maxResults=0) comment = '''This ticket is being closed for hygiene, as it is more than 12 months old. If the vulnerability still exists, a new ticket will be opened.''' for ticket in tickets_to_close: self.close_ticket(ticket, self.JIRA_RESOLUTION_OBSOLETE, comment) return 0 def project_exists(self, project): try: self.jira.project(project) return True except: return False return False def download_tickets(self, path): #saves all tickets locally, local snapshot of vulnerability_management ticktes #check if file already exists check_date = str(date.today()) fname = '{}jira_{}.json'.format(path, check_date) if os.path.isfile(fname): self.logger.info("File {} already exists, skipping ticket download".format(fname)) return True try: self.logger.info("Saving locally tickets from the last {} months".format(self.max_time_tracking)) jql = "labels=vulnerability_management AND created >=startOfMonth(-{})".format(self.max_time_tracking) tickets_data = self.jira.search_issues(jql, maxResults=0) #end of line needed, as writelines() doesn't add it automatically, otherwise one big line to_save = [json.dumps(ticket.raw.get('fields'))+"\n" for ticket in tickets_data] with open(fname, 'w') as outfile: outfile.writelines(to_save) self.logger.info("Tickets saved succesfully.") return True except Exception as e: self.logger.error("Tickets could not be saved locally: {}.".format(e)) return False
def main(arguments): parser = argparse.ArgumentParser() parser.add_argument('-c', '--config-file', required=True) parser.add_argument('-w', '--write-jira', action='store_true') args = parser.parse_args(arguments) settings = ConfigParser.ConfigParser() settings.read(args.config_file) # Connect to Bugzilla bz_url = settings.get('bugzilla', 'url') bz_user = settings.get('bugzilla', 'user') bz_pass = settings.get('bugzilla', 'pass') bz_product = settings.get('bugzilla', 'product') bzURL = 'https://%s:%s@%s' % (bz_user, bz_pass, bz_url) bzapi = bugzilla.Bugzilla(url=bzURL, user=bz_user, password=bz_pass, sslverify=False, use_creds=False) # Connect to JIRA jira_url = settings.get('jira', 'url') jira_user = settings.get('jira', 'user') jira_pass = settings.get('jira', 'pass') jira_product = settings.get('jira', 'product') jira_project = settings.get('jira', 'project') jac = JIRA(server=jira_url, basic_auth=(jira_user, jira_pass)) # Obtain Bugzilla bugs query = bzapi.build_query(product=bz_product) t1 = time.time() bugs = bzapi.query(query) t2 = time.time() print("Found %d bugs in BugZilla with our query" % len(bugs)) print("Quicker query processing time: %s" % (t2 - t1)) # Sync Bugzilla bugs with Jira # For simplicity and for reporting only 2 states are considered in JIRA # Open and Closed(Resolved) cnt_update = 0 cnt_new = 0 for bzbug in bugs: if bzbug.see_also: # Check if the bug exists in Jira and sync the status # we look for a JIRA in link in the see_also fields in bugzilla for url in bzbug.see_also: if jira_url in url: issue = jac.issue(url.rsplit('/',1)[-1]) # Assuming that JIRA issues will only have 2 status if the bug is # resolved in bugzilla we close it in JIRA if not args.write_jira and bzbug.status == "RESOLVED" and issue.fields.status == "Open": print("Sync status Bug id=%s summary=%s status=%s jira_status=%s" % (bzbug.id, bzbug.summary, status_mapping[bzbug.status], issue.fields.status)) elif bzbug.status == "RESOLVED" and issue.fields.status == "Open": # Close Issue jira.transition_issue(issue, '2') cnt_update += 1 else: print("Not need to Sync Bug id=%s summary=%s status=%s jira_status=%s" % (bzbug.id, bzbug.summary, status_mapping[bzbug.status], issue.fields.status)) # We assume 1<->1 links from bugzilla to jira break else: # Create Bugzilla bug in JIRA bzbug_url = 'https://%s/show_bug.cgi?id=%i' % (bz_url, bzbug.id) if args.write_jira: new_issue = jac.create_issue(project=jira_project, summary=bzbug.summary, labels=[jira_product, "bugzilla"], customfield_16700=bzbug_url, issuetype={'name': 'Bug'}) issue_url = 'https://%s/browse/%s' % (jira_url, new_issue.key) # add the JIRA link to the see_also field update = bzapi.build_update(see_also_add=[issue_url]) bzapi.update_bugs([bzbug.id], update) if status_mapping[bzbug.status] != new_issue.fields.status: sync_bug_status(jac, new_issue, bzbug.status, new_issue.fields.status) else: print("Create new Bug id=%s summary=%s status=%s" % (bzbug.id, bzbug.summary, bzbug.status)) cnt_new += 1 if args.write_jira: print("%i bugs will be created in JIRA" % cnt_new) print("%i bugs will be synced between JIRA and Bugzilla" % cnt_update)
class JiraTestManager(object): """ Used to instantiate and populate the JIRA instance with data used by the unit tests. Attributes: CI_JIRA_ADMIN (str): Admin user account name. CI_JIRA_USER (str): Limited user account name. max_retries (int): number of retries to perform for recoverable HTTP errors. """ __shared_state = {} def __init__(self): self.__dict__ = self.__shared_state if not self.__dict__: self.initialized = 0 try: if CI_JIRA_URL in os.environ: self.CI_JIRA_URL = os.environ['CI_JIRA_URL'] self.max_retries = 5 else: self.CI_JIRA_URL = "https://pycontribs.atlassian.net" self.max_retries = 5 if CI_JIRA_ADMIN in os.environ: self.CI_JIRA_ADMIN = os.environ['CI_JIRA_ADMIN'] else: self.CI_JIRA_ADMIN = 'ci-admin' if CI_JIRA_ADMIN_PASSWORD in os.environ: self.CI_JIRA_ADMIN_PASSWORD = os.environ[ 'CI_JIRA_ADMIN_PASSWORD'] else: self.CI_JIRA_ADMIN_PASSWORD = '******' if 'CI_JIRA_USER' in os.environ: self.CI_JIRA_USER = os.environ['CI_JIRA_USER'] else: self.CI_JIRA_USER = '******' if 'CI_JIRA_USER_PASSWORD' in os.environ: self.CI_JIRA_USER_PASSWORD = os.environ[ 'CI_JIRA_USER_PASSWORD'] else: self.CI_JIRA_USER_PASSWORD = '******' self.CI_JIRA_ISSUE = os.environ.get('CI_JIRA_ISSUE', 'Bug') if OAUTH: self.jira_admin = JIRA(oauth={ 'access_token': 'hTxcwsbUQiFuFALf7KZHDaeAJIo3tLUK', 'access_token_secret': 'aNCLQFP3ORNU6WY7HQISbqbhf0UudDAf', 'consumer_key': CONSUMER_KEY, 'key_cert': KEY_CERT_DATA}) else: if self.CI_JIRA_ADMIN: self.jira_admin = JIRA(self.CI_JIRA_URL, basic_auth=(self.CI_JIRA_ADMIN, self.CI_JIRA_ADMIN_PASSWORD), logging=False, validate=True, max_retries=self.max_retries) else: self.jira_admin = JIRA(self.CI_JIRA_URL, validate=True, logging=False, max_retries=self.max_retries) if self.jira_admin.current_user() != self.CI_JIRA_ADMIN: # self.jira_admin. self.initialized = 1 sys.exit(3) if OAUTH: self.jira_sysadmin = JIRA(oauth={ 'access_token': '4ul1ETSFo7ybbIxAxzyRal39cTrwEGFv', 'access_token_secret': 'K83jBZnjnuVRcfjBflrKyThJa0KSjSs2', 'consumer_key': CONSUMER_KEY, 'key_cert': KEY_CERT_DATA}, logging=False, max_retries=self.max_retries) else: if self.CI_JIRA_ADMIN: self.jira_sysadmin = JIRA(self.CI_JIRA_URL, basic_auth=(self.CI_JIRA_ADMIN, self.CI_JIRA_ADMIN_PASSWORD), logging=False, validate=True, max_retries=self.max_retries) else: self.jira_sysadmin = JIRA(self.CI_JIRA_URL, logging=False, max_retries=self.max_retries) if OAUTH: self.jira_normal = JIRA(oauth={ 'access_token': 'ZVDgYDyIQqJY8IFlQ446jZaURIz5ECiB', 'access_token_secret': '5WbLBybPDg1lqqyFjyXSCsCtAWTwz1eD', 'consumer_key': CONSUMER_KEY, 'key_cert': KEY_CERT_DATA}) else: if self.CI_JIRA_ADMIN: self.jira_normal = JIRA(self.CI_JIRA_URL, basic_auth=(self.CI_JIRA_USER, self.CI_JIRA_USER_PASSWORD), validate=True, logging=False, max_retries=self.max_retries) else: self.jira_normal = JIRA(self.CI_JIRA_URL, validate=True, logging=False, max_retries=self.max_retries) # now we need some data to start with for the tests # jira project key is max 10 chars, no letter. # [0] always "Z" # [1-6] username running the tests (hope we will not collide) # [7-8] python version A=0, B=1,.. # [9] A,B -- we may need more than one project prefix = 'Z' + (re.sub("[^A-Z]", "", getpass.getuser().upper()))[0:6] + \ chr(ord('A') + sys.version_info[0]) + \ chr(ord('A') + sys.version_info[1]) self.project_a = prefix + 'A' # old XSS self.project_a_name = "Test user=%s python=%s.%s A" \ % (getpass.getuser(), sys.version_info[0], sys.version_info[1]) self.project_b_name = "Test user=%s python=%s.%s B" \ % (getpass.getuser(), sys.version_info[0], sys.version_info[1]) self.project_b = prefix + 'B' # old BULK try: self.jira_admin.project(self.project_a) except Exception as e: logging.warning(e) pass else: self.jira_admin.delete_project(self.project_a) try: self.jira_admin.project(self.project_b) except Exception as e: logging.warning(e) pass else: self.jira_admin.delete_project(self.project_b) self.jira_admin.create_project(self.project_a, self.project_a_name) self.project_a_id = self.jira_admin.project(self.project_a).id self.jira_admin.create_project(self.project_b, self.project_b_name) self.project_b_issue1_obj = self.jira_admin.create_issue(project=self.project_b, summary='issue 1 from %s' % self.project_b, issuetype=self.CI_JIRA_ISSUE) self.project_b_issue1 = self.project_b_issue1_obj.key self.project_b_issue2_obj = self.jira_admin.create_issue(project=self.project_b, summary='issue 2 from %s' % self.project_b, issuetype={'name': self.CI_JIRA_ISSUE}) self.project_b_issue2 = self.project_b_issue2_obj.key self.project_b_issue3_obj = self.jira_admin.create_issue(project=self.project_b, summary='issue 3 from %s' % self.project_b, issuetype={'name': self.CI_JIRA_ISSUE}) self.project_b_issue3 = self.project_b_issue3_obj.key except Exception as e: # exc_type, exc_value, exc_traceback = sys.exc_info() formatted_lines = traceback.format_exc().splitlines() msg = "Basic test setup failed: %s\n\t%s" % ( e, "\n\t".join(formatted_lines)) logging.fatal(msg) self.initialized = 1 pytest.exit("FATAL") self.initialized = 1 else: # already exist but we need to be sure it was initialized counter = 0 while not self.initialized: sleep(1) counter += 1 if counter > 60: logging.fatal("Something is clearly not right with " + "initialization, killing the tests to prevent a " + "deadlock.") sys.exit(3)
def create_jira_ticket(summary, description, **kwargs): """ Create a new jira ticket, returning the associated number. Examples: Synchronously create a jira ticket:: create_jira_ticket("Test Ticket", "This is a test") Asynchronously create a jira ticket:: create_jira_ticket.delay("Test Ticket", "This is a test") Inputs: .. note:: watchers and watcher_group are mutually exclusive. :summary: The ticket summary :description: The ticket description :assignee: Who the ticket should be assigned to. Defaults to "-1" which is analogous to selecting "automatic" on the JIRA web form. :reporter: Who created the ticket (or is responsible for QCing it). Defaults to "automaticagent". :issuetype: The type of issue. Defaults to "Task". :project: The project the ticket should be created in. Defaults to "ST", which is Product Support. :priority: Ticket Priority. Defaults to "Major". :components: A list of components this ticket belongs to. :watchers: A list of user names to add as watchers of this ticket. :watcher_group: A group to assign as watchesr. Output: .. note:: The instance isn't returned because we need the ability to pass the results to another asynchronous task without blocking, which requires that all arguments be serializable. The ticket key which corresponds to the created JIRA ticket. """ jira = JIRA(options=options, basic_auth=housekeeping_auth) assignee = {'name': kwargs.setdefault('assignee', '-1')} reporter = {'name': kwargs.setdefault('reporter', 'automationagent')} issuetype = {'name': kwargs.setdefault('issuetype', 'Task')} project = {'key': kwargs.setdefault('project', 'ST')} priority = {'name': kwargs.setdefault('priority', 'Major')} components = [{'name': name} for name in kwargs.setdefault('components', [])] watchers = kwargs.setdefault('watchers', set()) if 'watcher_group' in kwargs: watchers = watchers.union( jira.group_members(kwargs['watcher_group']).keys()) if assignee == reporter: raise ValueError("Assignee and reporter must be different.") fields = { 'project': project, 'summary': summary, 'description': description, 'issuetype': issuetype, 'priority': priority, 'reporter': reporter, 'assignee': assignee, 'components': components, } issue = jira.create_issue(fields=fields) for watcher in watchers: jira.add_watcher(issue, watcher) return issue.key
'customfield_17680' : { 'value' : 'Low' }, # complexity of change 'customfield_17681' : { 'value' : 'Low' }, # Pre-Testing 'customfield_17682' : { 'value' : 'Low' }, # Scope of Validation 'customfield_17683' : { 'value' : 'Low' }, # Rollback Plan 'customfield_17684' : { 'value' : 'Low' }, # Customer landscape impact 'customfield_16583' : {'value':'App', 'child': {'value':'Update/Upgrade'}}, # Category(s) 'components' : [{'name': 'JAM'}], #Components 'customfield_15282' : {'value':'No Data Protection Regulation'}, # Customer Data Protection 'customfield_10802' : { 'value' : dc_total }, # Data Center 'customfield_16590' : { 'id' : time_Zone },# DC Time Zone 'customfield_10842' : { 'id' : env_type }, # Environment Type 'customfield_16794' : {'value':'None'}, # Customer Impact 'customfield_17055' : {'value':'No'}, # CCM Notify 'customfield_17056' : { 'value' : 'Not Required' }, # Maintenance Page Required? 'customfield_17057' : { 'value' : 'No' }, # Pop up enable required? 'customfield_16814' : actual_date+ 'T11:30:00.000-0700', # requested Start 'customfield_16815' : actual_date+ 'T11:30:00.000-0700', # requested End 'description' : descript, # description 'customfield_17426' : test_details, # Pre-Test Details 'customfield_17427' : justification, # Business Justification 'customfield_17058' : imp_steps, # Implementation Steps 'customfield_16800' : back_steps, # Backout Steps 'customfield_16801' : validate, # Validation Steps 'customfield_16802' : { 'name' : 'svishwanath' }, # Validator 'customfield_17059' : [{ 'value' : 'TechOwn - JAM' }], #Tech Approver 17059 'customfield_17060' : [{ 'value' : 'BusOwn - JAM' }], # Impact/Impl Approver 17060 } new_issue = jira.create_issue(fields=values) jira.assign_issue(new_issue, 'sureshkumara') print new_issue.key
def submit_jira_ticket(request): jira_setting = jirasetting.objects.all() for jira in jira_setting: jira_url = jira.jira_server username = jira.jira_username password = jira.jira_password jira_server = jira_url jira_username = signing.loads(username) jira_password = signing.loads(password) options = {'server': jira_server} jira_ser = JIRA(options, basic_auth=(jira_username, jira_password)) jira_projects = jira_ser.projects() if request.method == 'GET': summary = request.GET['summary'] description = request.GET['description'] scanner = request.GET['scanner'] vuln_id = request.GET['vuln_id'] scan_id = request.GET['scan_id'] return render(request, 'submit_jira_ticket.html', {'jira_projects': jira_projects, 'summary': summary, 'description': description, 'scanner': scanner, 'vuln_id': vuln_id, 'scan_id': scan_id }) if request.method == 'POST': summary = request.POST.get('summary') description = request.POST.get('description') project_id = request.POST.get('project_id') issue_type = request.POST.get('issue_type') vuln_id = request.POST.get('vuln_id') scanner = request.POST.get('scanner') scan_id = request.POST.get('scan_id') issue_dict = { 'project': {'id': project_id}, 'summary': summary, 'description': description, 'issuetype': {'name': issue_type}, } new_issue = jira_ser.create_issue(fields=issue_dict) print new_issue if scanner == 'zap': zap_scan_results_db.objects.filter(vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect('/webscanners/zap_vul_details/?scan_id=%s&scan_name=%s' % ( scan_id, summary ) ) elif scanner == 'burp': burp_scan_result_db.objects.filter(vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect('/webscanners/burp_vuln_out/?scan_id=%s&scan_name=%s' % ( scan_id, summary ) ) elif scanner == 'arachni': arachni_scan_result_db.objects.filter(vuln_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect('/webscanners/arachni_vuln_out/?scan_id=%s&scan_name=%s' % (scan_id, summary)) elif scanner == 'open_vas': ov_scan_result_db.objects.filter(vul_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect('/networkscanners/vul_details/?scan_id=%s' % scan_id) elif scanner == 'nessus': nessus_report_db.objects.filter(vul_id=vuln_id).update(jira_ticket=new_issue) return HttpResponseRedirect('/networkscanners/nessus_vuln_details/?scan_id=%s' % scan_id)
class Jira(object): """ jira operation class """ def __init__(self, **args): """ Init JIRA connection """ self.server = settings.CASELINK_JIRA['SERVER'] self.username = settings.CASELINK_JIRA['USER'] self.password = settings.CASELINK_JIRA['PASSWORD'] self.verify = False # TODO: move to settings self._jira = JIRA(options={ 'server': self.server, 'verify': self.verify, }, basic_auth=(self.username, self.password)) def search_issues(self, project_name, jql_str, fields=None): """ Search issue under the project and return result """ jql_str = "project = " + project_name + " and " + jql_str return self.jira_.search_issues(jql_str, maxResults=-1, fields=fields) def add_comment(self, issue, comment): """ Add comments in the issue """ if isinstance(issue, (str, unicode)): issue = self._jira.issue(issue) return self._jira.add_comment(issue, comment) def transition_issue(self, issue, status): """ Transition issue status to another """ self.jira_.transition_issue(issue, status) def get_watchers(self, issue): """ Get a watchers Resource from the server for an issue """ watcher_data = self.jira_.watchers(issue) return [str(w.key) for w in watcher_data.watchers] def add_watcher(self, issue, watchers): """ Append an issue's watchers list """ new_watchers = [] if isinstance(watchers, str): new_watchers.append(watchers) elif isinstance(watchers, list): new_watchers = watchers for watcher in new_watchers: self.jira_.add_watcher(issue, watcher) def remove_watcher(self, issue, watchers): """ Remove watchers from an issue's watchers list """ del_watchers = [] if isinstance(watchers, str): del_watchers.append(watchers) elif isinstance(watchers, list): del_watchers = watchers for watcher in del_watchers: self.jira_.remove_watcher(issue, watcher) def create_issue(self, issue_dict): """ Create Issue and apply some default properties """ dict_ = { 'project': { 'key': 'LIBVIRTAT', }, 'summary': None, 'description': None, 'priority': { 'name': 'Major', }, 'assignee': { 'name': None }, } parent_issue = issue_dict.pop('parent_issue', None) or settings.CASELINK_JIRA['DEFAULT_PARENT_ISSUE'] assignee = issue_dict.pop('assignee', None) or settings.CASELINK_JIRA['DEFAULT_ASSIGNEE'] dict_.update({ 'assignee': { 'name': assignee } }) if parent_issue: dict_.update({ 'parent': { 'id': parent_issue }, 'issuetype': { 'name': 'Sub-task' } }) else: dict_.update({ 'issuetype': { 'name': 'Task' } }) dict_.update(issue_dict) return self._jira.create_issue(dict_) # Helper functions def update_issue(self, issue, changes=None): trans = settings.CASELINK_JIRA['REOPEN_TRANSITION'] try: if isinstance(issue, (str, unicode)): issue = self._jira.issue(issue) return self._jira.transition_issue(issue, trans) except Exception: pass if changes: self.add_comment(issue, 'Polarion Workitem Changed: {code}%s{code}' % changes) def create_automation_request(self, wi_id, wi_title, wi_url, changes, assignee, parent_issue=None): description = AUTOMATION_REQUEST_DESCRIPTION.format( polarion_wi_id=wi_id, polarion_wi_title=wi_title, polarion_wi_url=wi_url, ) summary = AUTOMATION_REQUEST_SUMMARY.format( polarion_wi_id=wi_id, polarion_wi_title=wi_title ) assignee = assignee parent_issue = parent_issue return self.create_issue({ 'summary': summary, 'description': description, 'assignee': assignee, 'parent_issue': parent_issue }) def create_update_request(self, wi_id, wi_title, wi_url, changes, assignee, parent_issue=None): description = UPDATE_REQUEST_DESCRIPTION.format( polarion_wi_id=wi_id, polarion_wi_title=wi_title, polarion_wi_url=wi_url, polarion_wi_changes=changes ) summary = UPDATE_REQUEST_SUMMARY.format( polarion_wi_id=wi_id, polarion_wi_title=wi_title ) assignee = assignee parent_issue = parent_issue return self.create_issue({ 'summary': summary, 'description': description, 'assignee': assignee, 'parent_issue': parent_issue }) def get_issue_feedback(self, issue_key): issue = self._jira.issue(issue_key) status = str(issue.fields.status) resolution = str(issue.fields.resolution) comments = issue.fields.comment.comments if status == 'Done': for comment in reversed(comments): match = re.match(UPDATED_PATTERN_REGEX, comment.body, re.M) if match: return { "status": status, "resolution": resolution, "cases": filter(lambda x: x, map(lambda x: x.strip(), match.groupdict()['cases'].splitlines())), "prs": filter(lambda x: x, map(lambda x: x.strip(), match.groupdict()['prs'].splitlines())), } return { "status": status, "resolution": resolution, "cases": None, "prs": None, }
class Manager(object): """Issue manager.""" JINJA_ENV = jinja2.Environment(loader=jinja2.FileSystemLoader(ROOT_DIR)) SUMMARY_TMPL = JINJA_ENV.get_template("templates/summary.template") DESCRIPTION_TMPL = JINJA_ENV.get_template("templates/description.template") DESCRIPTION_BOUNDARY = "_-- Alertmanager -- [only edit above]_" # Order for the search query is important for the query performance. It relies # on the 'alert_group_key' field in the description that must not be modified. SEARCH_QUERY = ('project = "{project}" and ' 'issuetype = "{issuetype}" and ' 'labels = "alert" and ' "status not in ({status}) and " 'labels = "jiralert:{group_label_key}"') logger = logging.getLogger(__name__) def __init__( self, basic_auth=None, server=None, resolve_transitions=(), resolved_status=(), threadpool=None, ): self.jira = None self.basic_auth = basic_auth self.server = server self.resolve_transitions = resolve_transitions self.resolved_status = resolved_status self.threadpool = threadpool # TODO: Keep an history of the last handled payloads and associated tickets. # (updated or created). Display that on the UI. self.history = collections.deque(20 * [None], 20) def connect(self): self.logger.info("Connecting to %s" % self.server) self.jira = JIRA(basic_auth=self.basic_auth, server=self.server) self.logger.info("Connected to %s" % self.server) def ready(self): return bool(self.jira) def shutdown(self): self.jira.close() self.jira = None if self.threadpool: self.threadpool.stop() self.threadpool = None def record(self, project, issue_type, request, response): event = Event(project, issue_type, request, response) self.history.appendleft(event) def response(self, status, code, issues=None): return {"status": status, "issues": issues}, code @jira_errors_transitions.count_exceptions() @jira_request_time_transitions.time() def transitions(self, issue): return self.jira.transitions(issue) @jira_errors_close.count_exceptions() @jira_request_time_close.time() def close(self, issue, tid): return self.jira.transition_issue(issue, tid) @jira_errors_update.count_exceptions() @jira_request_time_update.time() def update_issue(self, issue, summary, description, tags): custom_desc = issue.fields.description.rsplit( self.DESCRIPTION_BOUNDARY, 1)[0] # Merge expected tags and existing ones fields = {"labels": list(set(issue.fields.labels + tags))} return issue.update( summary=summary, fields=fields, description="%s\n\n%s\n%s" % (custom_desc.strip(), self.DESCRIPTION_BOUNDARY, description), ) @jira_errors_create.count_exceptions() @jira_request_time_create.time() def create_issue(self, project, issue_type, summary, description, tags): return self.jira.create_issue({ "project": { "key": project }, "summary": summary, "description": "%s\n\n%s" % (self.DESCRIPTION_BOUNDARY, description), "issuetype": { "name": issue_type }, "labels": tags, }) @request_time_generic_issues.time() def post_issues(self, payload): """ This endpoint accepts a JSON encoded notification according to the version 3 or 4 of the generic webhook of the Prometheus Alertmanager. """ common_labels = payload["commonLabels"] if "issue_type" not in common_labels or "project" not in common_labels: self.logger.error( "/issue, required commonLabels not found: issue_type or project" ) project = None issue_type = None resp = self.response( "Required commonLabels not found: issue_type or project", 400) else: issue_type = common_labels["issue_type"] project = common_labels["project"] resp = self.do_file_issue(project, issue_type, payload) self.record(project, issue_type, payload, resp) return resp @request_time_qualified_issues.time() def post_issues_with_project(self, project, issue_type, payload): """ This endpoint accepts a JSON encoded notification according to the version 3 or 4 of the generic webhook of the Prometheus Alertmanager. """ if payload["version"] not in ["3", "4"]: self.logger.error("/issue, unknown message version: %s" % payload["version"]) resp = self.response( "unknown message version %s" % payload["version"], 400) else: resp = self.do_file_issue(project, issue_type, payload) self.record(project, issue_type, payload, resp) return resp def update_or_resolve_issue(self, project, issue_type, issue, resolved, summary, description, tags): """Update and maybe resolve an issue.""" is_closed = False self.logger.debug("issue (%s, %s), jira issue found: %s" % (project, issue_type, issue.key)) # Try different possible transitions for resolved incidents # in order of preference. Different ones may work for different boards. if resolved: valid_trans = [ t for t in self.transitions(issue) if t["name"].lower() in self.resolve_transitions ] if valid_trans: self.close(issue, valid_trans[0]["id"]) is_closed = True else: self.logger.warning("Unable to find transition to close %s" % issue) # Update the base information regardless of the transition. self.update_issue(issue, summary, description, tags) self.logger.info("issue (%s, %s), %s updated" % (project, issue_type, issue.key)) return is_closed def do_file_issue(self, project, issue_type, payload): if not self.ready(): return self.response("Not ready yet", 503) if payload["version"] not in ["3", "4"]: self.logger.error("issue (%s, %s), unknown message version: %s" % (project, issue_type, payload["version"])) return self.response( "unknown message version %s" % payload["version"], 400) if self.threadpool: # We want a separate thread pool here to avoid blocking incoming # requests. self.threadpool.callInThread(self.do_file_issue_async, project, issue_type, payload) return self.response("OK (async)", 201) else: issues = self.do_file_issue_sync(project, issue_type, payload) return self.response("OK", 200, issues) def do_file_issue_async(self, project, issue_type, data): try: issues = self.do_file_issue_sync(project, issue_type, data) resp = self.response("OK", 200, issues) except JIRAError as e: resp = self.response(str(e), 503) # Record a fake response for async requests. self.record(project, issue_type, data, resp) @staticmethod def prepare_data(data): # Patch data to make sure it has all we need. data = copy.deepcopy(data) if "alerts" not in data: data["alerts"] = [] for alert in data["alerts"]: # Generate a short hash to make sorting more stable. simple_alert = copy.deepcopy(alert) # Remove things that change all the time. if "startsAt" in simple_alert: del simple_alert["startsAt"] if "endsAt" in simple_alert: del simple_alert["endsAt"] alert["hash"] = hashlib.sha1( json.dumps(simple_alert, sort_keys=True).encode()).hexdigest()[:8] return data @errors.count_exceptions() def do_file_issue_sync(self, project, issue_type, data): issues = {"created": [], "found": [], "updated": [], "resolved": []} self.logger.info("issue: %s %s" % (project, issue_type)) data = self.prepare_data(data) resolved = data["status"] == "resolved" tags = prepare_tags(data["commonLabels"]) tags.append("jiralert:%s" % prepare_group_label_key(data["groupKey"])) description = self.DESCRIPTION_TMPL.render(data) summary = self.SUMMARY_TMPL.render(data) # If there's already a ticket for the incident, update it and close if necessary. query = self.SEARCH_QUERY.format( project=project, issuetype=issue_type, status=",".join(self.resolved_status), group_label_key=prepare_group_label_key(data["groupKey"]), ) self.logger.debug(query) result = self.jira.search_issues(query) or [] # sort issue by key to have them in order of creation. sorted(result, key=lambda i: i.key) issues["found"] = [issue.permalink() for issue in result] for issue in result: is_closed = self.update_or_resolve_issue(project, issue_type, issue, resolved, summary, description, tags) issues["resolved" if is_closed else "updated"].append( issue.permalink()) if not result: # Do not create an issue for resolved incidents that were never filed. if not resolved: issue = self.create_issue(project, issue_type, summary, description, tags) issues["created"].append(issue.permalink()) self.logger.info("issue (%s, %s), new issue created (%s)" % (project, issue_type, issue.key)) return issues
def quote(x): return '"' + x + '"' if not options.componentjbide and not options.componentjbds: rootJBDS_dict = { 'project' : { 'key': 'JBDS' }, 'summary' : 'For JBDS ' + jbds_fixversion + ': ' + taskdescription, 'description' : 'For JBDS ' + jbds_fixversion + ': ' + taskdescriptionfull + '\n\n[Search for all task JIRA|' + tasksearch + ']', 'issuetype' : { 'name' : 'Task' }, 'priority' : { 'name' :'Blocker'}, 'fixVersions' : [{ "name" : jbds_fixversion }], 'components' : [{ "name" : "installer" }], 'labels' : [ "task" ], } rootJBDS = jira.create_issue(fields=rootJBDS_dict) installerLead = queryComponentLead(CLJBDS, 'installer', 0) try: jira.assign_issue(rootJBDS, installerLead) except: if (not options.jiraonly): print "[WARNING] Unexpected error! User {0} tried to assign {1} to {2}: {3}".format(options.usernameJIRA, rootJBDS, installerLead, sys.exc_info()[0]) if (options.jiraonly): print(rootJBDS.key) else: print("Task JIRA created for this milestone include:") print("") print("JBDS : " + jiraserver + '/browse/' + rootJBDS.key + " => " + installerLead) rootJBIDE_dict = { 'project' : { 'key': 'JBIDE' },
failureDetails = failureDetails + '{code:title=' + testurl + '' + className_re + '/' + name_re + '}\n' failureDetails = failureDetails + prettyXML(s) failureDetails = failureDetails + '\n{code}\n\n' #print failureDetails rootJBIDE_dict = { 'project' : { 'key': 'JBIDE' }, 'summary' : str(len(testcaselist)) + ' Test Failure(s) in JBIDE ' + jbide_affectedversion + ' for ' + component + ' component', 'description' : failureSummary + failureDetails, 'issuetype' : { 'name' : 'Task' }, 'priority' : { 'name' :'Critical'}, 'versions' : [{ "name" : jbide_affectedversion }], 'components' : [{ "name" : component }], 'labels' : [ "testfailure" ] } jira = JIRA(options={'server':jiraserver}, basic_auth=(options.usernameJIRA, options.passwordJIRA)) CLJBIDE = jira.project_components(jira.project('JBIDE')) # full list of components in JBIDE rootJBIDE = jira.create_issue(fields=rootJBIDE_dict) componentLead = queryComponentLead(CLJBIDE, component, 0) try: jira.assign_issue(rootJBIDE, componentLead) except: print "[WARNING] Unexpected error! User {0} tried to assign {1} to {2}: {3}".format(options.usernameJIRA, rootJBIDE, componentLead, sys.exc_info()[0]) accept = raw_input("\nAccept new JIRA " + jiraserver + '/browse/' + rootJBIDE.key + " => " + componentLead + " ? [Y/n] ") if accept.capitalize() in ["N"] : rootJBIDE.delete() # see JIRA_components listing in components.py # Sample usage: see createTestFailureJIRA.py.examples.txt
class JiraTool: def __init__(self): self.server = jira_url self.basic_auth = (usrer, password) self.jiraClinet = None def login(self): self.jiraClinet = JIRA(server=self.server, basic_auth=self.basic_auth) if self.jiraClinet != None: return True else: return False def findIssueById(self, issueId): if issueId: if self.jiraClinet == None: self.login() return self.jiraClinet.issue(issueId) else: return 'Please input your issueId' def deleteAllIssue(self, project): project_issues = self.jiraClinet.search_issues('project=' + project.name) for issue in project_issues: logging.info('delete issue %s' % (issue)) issue.delete() def deleteAllSprint(self, board): sprints = self.jiraClinet.sprints(board.id) for sprint in sprints: logging.info('delete sprint %s' % (sprint.name)) sprint.delete() def getProject(self, name): projects = self.jiraClinet.projects() #logging.info("get project %s" %(name)) for project in projects: #logging.info("project %s" %(project.name)) if (name == project.name): return project return None #return self.jiraClinet.create_project(key='SCRUM', name=name, assignee='yujiawang', type="Software", template_name='Scrum') def getBoard(self, project, name): boards = self.jiraClinet.boards() for board in boards: if (name == board.name): logging.info("board:%s id:%d" % (board.name, board.id)) return board return self.jiraClinet.create_board(name, [project.id]) def createSprint(self, board, name, startDate=None, endDate=None): logging.info("==== create sprint[%s] in board[%s] ====>" % (name, board.name)) sprint = self.jiraClinet.create_sprint(name, board.id, startDate, endDate) return sprint def getSprint(self, board_id, sprint_name): sprints = self.jiraClinet.sprints(board_id) for sprint in sprints: if sprint.name == sprint_name: return sprint return None def createEpicTask(self, project, sprint, summary, description, assignee, participant, duedate): issue_dict = { 'project': { 'key': project.key }, 'issuetype': { 'id': issuetypes['Epic'] }, 'customfield_10002': summary, # epic 名称 'summary': summary, 'description': description, "customfield_10004": sprint.id, # sprint 'assignee': { 'name': assignee }, 'customfield_10303': participant, 'customfield_10302': '2018-08-24T05:41:00.000+0800' } logging.info(duedate) logging.info(issue_dict) #juse for debug issue = self.jiraClinet.create_issue(issue_dict) self.jiraClinet.add_issues_to_sprint(sprint.id, [issue.raw['key']]) logging.info( "===> add epic task[%s key:%s] to sprint [%s]" % (issue.raw['fields']['summary'], issue.raw['key'], sprint.name)) #dumpIssue(issue) #juse for debug return issue def createTask(self, project, sprint, epic, summary, description, assignee, participant): issue_dict = { 'project': { 'key': project.key }, 'issuetype': { 'id': issuetypes['Task'] }, 'summary': summary, 'description': description, 'assignee': { 'name': assignee }, 'customfield_10303': participant } issue = self.jiraClinet.create_issue(issue_dict) logging.info("==> add task[%s key:%s] link epic [%s key: %s]" % (issue.raw['fields']['summary'], issue.raw['key'], epic.raw['fields']['summary'], epic.raw['key'])) self.jiraClinet.add_issues_to_epic(epic.id, [issue.raw['key']]) self.jiraClinet.add_issues_to_sprint(sprint.id, [issue.raw['key']]) return issue def createSubTask(self, project, parent, summary, description, assignee, participant): issue_dict = { 'project': { 'key': project.key }, 'parent': { 'key': parent.raw['key'] }, 'issuetype': { 'id': issuetypes['Sub-Task'] }, 'summary': summary, 'description': description, 'assignee': { 'name': assignee }, 'customfield_10303': participant } issue = self.jiraClinet.create_issue(issue_dict) logging.info("=> add sub task[%s key:%s] to task [%s key: %s]" % (issue.raw['fields']['summary'], issue.raw['key'], parent.raw['fields']['summary'], parent.raw['key'])) return issue
#!/usr/bin/env python import requests, json import os from jira import JIRA from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) options = {'verify': False, 'server': 'https://jira.name.com'} WORK_DIR = os.path.dirname(os.path.realpath(__file__)) CREDENTIAL_FILE = 'credentials.json' with open(WORK_DIR + '/' + CREDENTIAL_FILE, 'r') as file: credential = json.load(file) jira = JIRA(options, basic_auth=(credential["jira"]["username"], credential["jira"]['password'])) user_name = credential["jira"]["username"] user_pass = credential["jira"]['password'] new_issue = jira.create_issue(project={'key': 'PROJECTNAME'}, summary='Test Summary', description='Look into this one', issuetype={'name': 'Incident'}, customfield_10321={'value': 'prod'})
import os # count the site username = os.environ.get('JIRA_USER') password = os.environ.get('JIRA_PASS') jira_url = os.environ.get('JIRA_URL') jira = JIRA(jira_url, basic_auth=(username, password)) with open("patching.yml", 'r') as stream: tasks = yaml.load(stream) parent = jira.issue(tasks['parent']) print(parent.key) for task in tasks['stages']: subtask_dict = { 'project' : { 'key': 'OP' }, 'summary' : task, 'description' : '', 'issuetype' : { 'name' : 'Sub-task' }, 'parent' : { 'id' : parent.key}, 'components' : [{ 'id' :'12510'}], # Security 'customfield_13692' : 'none', # sitename 'customfield_10795' : [ { 'id' :'10717'}], # ACE } child = jira.create_issue(fields=subtask_dict) print(task)
class JiraTestManager: """Instantiate and populate the JIRA instance with data for tests. Attributes: CI_JIRA_ADMIN (str): Admin user account name. CI_JIRA_USER (str): Limited user account name. max_retries (int): number of retries to perform for recoverable HTTP errors. initialized (bool): if init was successful. """ __shared_state: Dict[Any, Any] = {} def __init__(self, jira_hosted_type=os.environ.get("CI_JIRA_TYPE", "Server")): """Instantiate and populate the JIRA instance""" self.__dict__ = self.__shared_state if not self.__dict__: self.initialized = False self.max_retries = 5 self._cloud_ci = False if jira_hosted_type and jira_hosted_type.upper() == "CLOUD": self.set_jira_cloud_details() self._cloud_ci = True else: self.set_jira_server_details() jira_class_kwargs = { "server": self.CI_JIRA_URL, "logging": False, "validate": True, "max_retries": self.max_retries, } self.set_basic_auth_logins(**jira_class_kwargs) if not self.jira_admin.current_user(): self.initialized = True sys.exit(3) # now we need to create some data to start with for the tests self.create_some_data() if not hasattr(self, "jira_normal") or not hasattr(self, "jira_admin"): pytest.exit("FATAL: WTF!?") if self._cloud_ci: self.user_admin = self.jira_admin.search_users( query=self.CI_JIRA_ADMIN)[0] self.user_normal = self.jira_admin.search_users( query=self.CI_JIRA_USER)[0] else: self.user_admin = self.jira_admin.search_users( self.CI_JIRA_ADMIN)[0] self.user_normal = self.jira_admin.search_users( self.CI_JIRA_USER)[0] self.initialized = True def set_jira_cloud_details(self): self.CI_JIRA_URL = "https://pycontribs.atlassian.net" self.CI_JIRA_ADMIN = os.environ["CI_JIRA_CLOUD_ADMIN"] self.CI_JIRA_ADMIN_PASSWORD = os.environ["CI_JIRA_CLOUD_ADMIN_TOKEN"] self.CI_JIRA_USER = os.environ["CI_JIRA_CLOUD_USER"] self.CI_JIRA_USER_PASSWORD = os.environ["CI_JIRA_CLOUD_USER_TOKEN"] self.CI_JIRA_ISSUE = os.environ.get("CI_JIRA_ISSUE", "Bug") def set_jira_server_details(self): self.CI_JIRA_URL = os.environ["CI_JIRA_URL"] self.CI_JIRA_ADMIN = os.environ["CI_JIRA_ADMIN"] self.CI_JIRA_ADMIN_PASSWORD = os.environ["CI_JIRA_ADMIN_PASSWORD"] self.CI_JIRA_USER = os.environ["CI_JIRA_USER"] self.CI_JIRA_USER_PASSWORD = os.environ["CI_JIRA_USER_PASSWORD"] self.CI_JIRA_ISSUE = os.environ.get("CI_JIRA_ISSUE", "Bug") def set_basic_auth_logins(self, **jira_class_kwargs): if self.CI_JIRA_ADMIN: self.jira_admin = JIRA( basic_auth=(self.CI_JIRA_ADMIN, self.CI_JIRA_ADMIN_PASSWORD), **jira_class_kwargs, ) self.jira_sysadmin = JIRA( basic_auth=(self.CI_JIRA_ADMIN, self.CI_JIRA_ADMIN_PASSWORD), **jira_class_kwargs, ) self.jira_normal = JIRA( basic_auth=(self.CI_JIRA_USER, self.CI_JIRA_USER_PASSWORD), **jira_class_kwargs, ) else: raise RuntimeError( "CI_JIRA_ADMIN environment variable is not set/empty.") def _project_exists(self, project_key: str) -> bool: """True if we think the project exists, else False. Assumes project exists if unknown Jira exception is raised. """ try: self.jira_admin.project(project_key) except JIRAError as e: # If the project does not exist a warning is thrown if "No project could be found" in str(e): return False LOGGER.exception("Assuming project '%s' exists.", project_key) return True def _remove_project(self, project_key): """Ensure if the project exists we delete it first""" wait_between_checks_secs = 2 time_to_wait_for_delete_secs = 40 wait_attempts = int(time_to_wait_for_delete_secs / wait_between_checks_secs) # TODO(ssbarnea): find a way to prevent SecurityTokenMissing for On Demand # https://jira.atlassian.com/browse/JRA-39153 if self._project_exists(project_key): try: self.jira_admin.delete_project(project_key) except Exception: LOGGER.exception("Failed to delete '%s'.", project_key) # wait for the project to be deleted for _ in range(1, wait_attempts): if not self._project_exists(project_key): # If the project does not exist a warning is thrown # so once this is raised we know it is deleted successfully break sleep(wait_between_checks_secs) if self._project_exists(project_key): raise TimeoutError( " Project '{project_key}' not deleted after {time_to_wait_for_delete_secs} seconds" ) def _create_project(self, project_key: str, project_name: str, force_recreate: bool = False) -> int: """Create a project and return the id""" if not force_recreate and self._project_exists(project_key): pass else: self._remove_project(project_key) create_attempts = 6 for _ in range(create_attempts): try: if self.jira_admin.create_project(project_key, project_name): break except JIRAError as e: if "A project with that name already exists" not in str(e): raise e return self.jira_admin.project(project_key).id def create_some_data(self): """Create some data for the tests""" # jira project key is max 10 chars, no letter. # [0] always "Z" # [1-6] username running the tests (hope we will not collide) # [7-8] python version A=0, B=1,.. # [9] A,B -- we may need more than one project """ `jid` is important for avoiding concurrency problems when executing tests in parallel as we have only one test instance. jid length must be less than 9 characters because we may append another one and the Jira Project key length limit is 10. """ self.jid = get_unique_project_name() self.project_a = self.jid + "A" # old XSS self.project_a_name = "Test user={} key={} A".format( getpass.getuser(), self.project_a, ) self.project_b = self.jid + "B" # old BULK self.project_b_name = "Test user={} key={} B".format( getpass.getuser(), self.project_b, ) self.project_sd = self.jid + "C" self.project_sd_name = "Test user={} key={} C".format( getpass.getuser(), self.project_sd, ) self.project_a_id = self._create_project(self.project_a, self.project_a_name) self.project_b_id = self._create_project(self.project_b, self.project_b_name, force_recreate=True) sleep(1) # keep it here as often Jira will report the # project as missing even after is created project_b_issue_kwargs = { "project": self.project_b, "issuetype": { "name": self.CI_JIRA_ISSUE }, } self.project_b_issue1_obj = self.jira_admin.create_issue( summary="issue 1 from %s" % self.project_b, **project_b_issue_kwargs) self.project_b_issue1 = self.project_b_issue1_obj.key self.project_b_issue2_obj = self.jira_admin.create_issue( summary="issue 2 from %s" % self.project_b, **project_b_issue_kwargs) self.project_b_issue2 = self.project_b_issue2_obj.key self.project_b_issue3_obj = self.jira_admin.create_issue( summary="issue 3 from %s" % self.project_b, **project_b_issue_kwargs) self.project_b_issue3 = self.project_b_issue3_obj.key
options = { 'server': cfg['servercfg']['server'], } jira = JIRA(options, basic_auth=(cfg['servercfg']['user'],cfg['servercfg']['pwd'])) print("connected") issue_dict = { 'project': 'SD', 'summary': 'New issue from jira-python', 'description': 'Look into this one', 'issuetype': {'name': 'Incidente'}, 'customfield_12100': 'Zabbix', 'customfield_12106': '4', 'customfield_12107': '32', 'customfield_12108': '224', 'customfield_11402': '9999', } new_issue = jira.create_issue(fields=issue_dict) print(new_issue) #Elemento 12108 = 224 #Descrição do Serviço 12107= 32 #Categoria 12106 = 4 #Solicitante 12100
gcissues_list = [] #-----guarda os objetos issue em uma lista for issue in pjissues: pjissues_list.append(str(issue)) gcquery = 'project = GESTCONFIG and status not in (Resolved,Closed,Verification,Rejected) and Referência is null' gcissues = jira.search_issues(gcquery) cont = 0 for issue in pjissues: issue = jira.issue(pjissues_list[cont]) project = issue.fields.project.key description = issue.fields.description summary = issue.fields.summary print('teste jira :|'+project+' | '+summary+' | '+description) new_issue = jira.create_issue(project='', summary=summary, description=description, issuetype={'name': 'Task'}) issue.update(customfield_10151=str(new_issue.key)) cont = cont+1 print(pjissues_list) #-------adiciona issue na base interna pela quantidade de referencias nulls na base externa # #após criar issue na base interna , editar o issue externo e adicionar o ID da interno como referencia
class TestIssues(unittest.TestCase): def setUp(self): self.jira = JIRA(options=dict(server=TEST_URL, verify=False), basic_auth=(TEST_USERNAME, TEST_PASSWORD)) self.issue1 = self.jira.create_issue( project='KB', summary='Test-1', issuetype={'name': 'Bug'}, ) self.issue2 = self.jira.create_issue( project='KB', summary='Test-2', issuetype={'name': 'Bug'}, ) def tearDown(self): issues = self.jira.search_issues('project = "KB" AND summary ~ "Test*"', fields=['key']) for _ in issues: _.delete() def assert_single_attachment(self): # TODO - Find how to test this automatically pass def assert_single_comment_with(self, text): comments = self.jira.comments(self.issue1.key) self.assertEqual(len(comments), 1) self.assertIn(text, comments[0].body) def test_new(self): result = CliRunner().invoke(topcli, ['issue', 'new', 'KB', 'task', 'Test-new']) self.assertEqual(result.exit_code, 0) issues = self.jira.search_issues('project = "KB" AND summary ~ "Test-new"', fields=['key', 'summary']) self.assertEqual(len(issues), 1) self.assertIn(issues[0].key, result.output) def test_transition(self): result = CliRunner().invoke(topcli, ['issue', 'transition', self.issue1.key, 'Done']) self.assertEqual(result.exit_code, 0) def test_assign(self): result = CliRunner().invoke(topcli, ['issue', 'assign', self.issue1.key, TEST_USERNAME]) self.assertEqual(result.exit_code, 0) assignee = self.jira.issue(self.issue1.key, fields=['assignee']).fields.assignee self.assertEqual(assignee.key, TEST_USERNAME) def test_unassign(self): result = CliRunner().invoke(topcli, ['issue', 'assign', self.issue1.key, TEST_USERNAME]) result = CliRunner().invoke(topcli, ['issue', 'unassign', self.issue1.key]) self.assertEqual(result.exit_code, 0) assignee = self.jira.issue(self.issue1.key, fields=['assignee']).fields.assignee self.assertIsNone(assignee) def test_attach_file(self): with CliRunner().isolated_filesystem() as dir_path: with open('data.txt', 'w') as f: print('abc', file=f) result = CliRunner().invoke(topcli, ['issue', 'attach', self.issue1.key, 'data.txt']) self.assertEqual(result.exit_code, 0) self.assert_single_attachment() def test_comment_args(self): result = CliRunner().invoke(topcli, ['issue', 'comment', self.issue1.key, 'Comment', 'from args']) self.assertEqual(result.exit_code, 0) self.assert_single_comment_with('Comment from args') def test_comment_file(self): with CliRunner().isolated_filesystem() as dir_path: with open('comment.txt', 'w') as f: print('Comment from file', file=f) result = CliRunner().invoke(topcli, ['issue', 'comment', self.issue1.key, 'comment.txt']) self.assertEqual(result.exit_code, 0) self.assert_single_comment_with('Comment from file') def test_comment_prompt(self): result = CliRunner().invoke(topcli, ['issue', 'comment', self.issue1.key], input='Comment from prompt\n') self.assertEqual(result.exit_code, 0) self.assert_single_comment_with('Comment from prompt') def test_comment_stdin(self): result = CliRunner().invoke(topcli, ['issue', 'comment', self.issue1.key, '-'], input='Comment\nfrom\nstdin') self.assertEqual(result.exit_code, 0) self.assert_single_comment_with('Comment\nfrom\nstdin') def test_link(self): result = CliRunner().invoke(topcli, ['issue', 'link', self.issue1.key, self.issue2.key, '-t', 'duplicates']) self.assertEqual(result.exit_code, 0) links = self.jira.issue(self.issue1.key, fields=['issuelinks']).fields.issuelinks self.assertEqual(len(links), 1) self.assertEqual(links[0].outwardIssue.key, self.issue2.key) self.assertEqual(links[0].type.outward, 'duplicates') def test_unlink(self): result = CliRunner().invoke(topcli, ['issue', 'link', self.issue1.key, self.issue2.key, '-t', 'duplicates']) self.assertEqual(result.exit_code, 0) result = CliRunner().invoke(topcli, ['issue', 'unlink', self.issue1.key, self.issue2.key]) links = self.jira.issue(self.issue1.key, fields=['issuelinks']).fields.issuelinks self.assertEqual(len(links), 0) def test_search_issue(self): result = CliRunner().invoke(topcli, ['issue', 'search']) self.assertEqual(result.exit_code, 0) self.assertIn('KB-1', result.output) self.assertIn('KB-2', result.output) self.assertIn('KB-3', result.output)
class JiraOperations(object): """ Base class for interaction with JIRA """ def __init__(self, config): # do not print excess warnings urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # JIRA configuration from config.json/DDB self.config = config # JIRA url self.server = self.config.jira.server # JIRA established session self.session = None if self.config.jira.enabled: self.login_oauth() else: logging.debug("JIRA integration is disabled") @property def current_user(self): """ :return: JIRA user name, used for connection establishing """ return self.session.current_user() def login_oauth(self): """ Establish JIRA connection using oauth :return: boolean, if connection was successful. """ if not self.config.jira.credentials: logging.error("Failed to login jira (empty credentials)") return False try: self.session = JIRA(options={ 'server': self.server, 'verify': False }, oauth=self.config.jira.credentials["oauth"]) except JIRAError: logging.exception( f"Failed to create oauth session to {self.server}") return False logging.debug( f'JIRA session to {self.server} created successfully (oauth)') return True def login_basic(self): """ Establish JIRA connection using basic authentication :return: boolean, if connection was successful. """ if not self.config.jira.credentials: logging.error("Failed to login jira (empty credentials)") return False username = self.config.jira.credentials["basic"]["username"] password = self.config.jira.credentials["basic"]["password"] options = {'server': self.server, 'verify': False} try: self.session = JIRA(options, basic_auth=(username, password)) except Exception: logging.exception( f"Failed to create basic session to {self.server}") return False logging.debug( f'JIRA session to {self.server} created successfully (basic)') return True def ticket_url(self, ticket_id): """ :return: URL to `ticket_id` """ return f"{self.server}/browse/{ticket_id}" def ticket_assignee(self, ticket_id): """ :param ticket_id: JIRA ticket :return: name of current assignee for ticket """ ticket = self.session.issue(ticket_id) return ticket.fields.assignee.name def find_valid_assignee(self, project, assignees): """ Check what record from given list of possible assignees can be used as assignee for given project. :param project: name of Jira project to perform check against :param assignees: list of possible assignees :return: """ for assignee in assignees: if assignee is None: continue try: users = self.session.search_assignable_users_for_projects( assignee, project) except Exception: continue # check only exact matches if len(users) == 1: return users[0].name return None def create_ticket(self, issue_data): """ Create a JIRA ticket :param issue_data: a dict containing field names and the values to use """ resp = self.session.create_issue(fields=issue_data) logging.debug(f"Created jira ticket {self.ticket_url(resp.key)}") return resp.key def create_issue_link(self, inward_issue, outward_issue): """ Linking JIRA tickets with 'relates to' link :return: boolean, if linking was successful """ if not (inward_issue or outward_issue): return False try: # JIRA comes with default types of links: # 1) relates to / relates to, # 2) duplicates / is duplicated by, # 3) blocks / is blocked by # 4) clones / is cloned by link_type = "relates to" self.session.create_issue_link(type=link_type, inwardIssue=inward_issue, outwardIssue=outward_issue) except Exception: logging.exception( f"Failed to create issue link {inward_issue} -> {outward_issue}" ) return False logging.debug(f"Created issue link {inward_issue} -> {outward_issue}") return True def assign_user(self, ticket_id, assinee_name): """ Assign `ticket_id` to `assinee_name`. :return: boolean, if assigning was successful """ if not (ticket_id or assinee_name): return False try: issue = self.session.issue(ticket_id) issue.update(assignee={'name': assinee_name}) except Exception: logging.exception( f"Failed to assign {ticket_id} to {assinee_name}") return False logging.debug(f"Assigned {ticket_id} to {assinee_name}") return True def update_ticket(self, ticket_id, updated_issue_data): """ Update JIRA ticket fields as in self.create_ticket(), but for existing ticket :param ticket_id: ticket Id to update :param updated_issue_data: a dict containing field names and the values to use :return: boolean, if updating was successful """ try: issue = self.session.issue(ticket_id) issue.update(updated_issue_data) except Exception: logging.exception(f"Failed to update {ticket_id}") return False logging.debug(f"Updated {ticket_id}") return True def add_comment(self, ticket_id, comment): """ Add comment to JIRA ticket :param ticket_id: ticket Id to add comment to :param comment: comment text :return: boolean, if operation was successful """ if ticket_id and comment: try: self.session.add_comment(ticket_id, comment) except Exception: logging.exception(f"Failed to add comment to {ticket_id}") return False return True def close_issue(self, ticket_id): """ Transition of ticket to `Closed` state. It checks if issue can be transitioned to `Closed` state. :param ticket_id: ticket Id to close :return: nothing """ issue = self.session.issue(ticket_id) if issue.fields.status.name == "Closed": logging.debug(f"{ticket_id} is already closed") return for transition in self.session.transitions(issue): if transition['name'] == 'Close Issue': self.session.transition_issue(ticket_id, transition['id']) logging.debug(f"Closed {ticket_id}") break else: logging.error(f"{self.ticket_url(ticket_id)} can't be closed") return def resolve_issue(self, ticket_id): """ Transition of ticket to `Resolved` state. It checks if issue can be transitioned to `Resolved` state. :param ticket_id: ticket Id to resolve :return: nothing """ issue = self.session.issue(ticket_id) if issue.fields.status.name == "Resolved": logging.debug(f"{ticket_id} is already resolved") return for transition in self.session.transitions(issue): if transition['name'] == 'Resolve Issue': self.session.transition_issue(ticket_id, transition['id']) logging.debug(f"Resolved {ticket_id}") break else: logging.error(f"{self.ticket_url(ticket_id)} can't be resolved") return def reopen_issue(self, ticket_id): """ Transition of ticket to `Reopen Issue` state. It checks if issue can be transitioned to `Reopen Issue` state. :param ticket_id: ticket Id to reopen :return: nothing """ issue = self.session.issue(ticket_id) if issue.fields.status.name in ["Open", "Reopened"]: logging.debug(f"{ticket_id} is already opened") return for transition in self.session.transitions(issue): if transition['name'] == 'Reopen Issue': self.session.transition_issue(ticket_id, transition['id']) logging.debug(f"Reopened {ticket_id}") break else: logging.error(f"{self.ticket_url(ticket_id)} can't be reopened") return def add_attachment(self, ticket_id, filename, text): """ Add text as attachment with filename to JIRA ticket :param ticket_id: ticket Id to add attachment to :param filename: label for attachment :param text: attachment text :return: attachment object """ attachment = io.StringIO(text) filename = filename.replace(':', '-') return self.session.add_attachment(issue=ticket_id, attachment=attachment, filename=filename) @staticmethod def build_tags_table(tags): """ Build JIRA table from AWS tags dictionary :param tags: dict with tags :return: str with JIRA table """ if not tags: return "" desc = f"*Tags*:\n" desc += f"||Key||Value||\n" for key, value in tags.items(): desc += f"|{key}|{empty_converter(value)}|\n" return desc