From 7e63d9426b1c07ab697abf041d21dac36f40cc6d Mon Sep 17 00:00:00 2001 From: Nathan Weinberg Date: Mon, 27 Apr 2020 15:15:43 -0400 Subject: [PATCH 1/4] Adding some additional tests --- test_functions.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test_functions.py b/test_functions.py index b67a7cd..ca47360 100644 --- a/test_functions.py +++ b/test_functions.py @@ -1,6 +1,24 @@ from functions import * +def test_get_bugs_set(): + mockers = { + 'job1': {'bz': [0]}, + 'job2': {'bz': [123456]}, + 'job3': {'bz': [123456, 789123]} + } + assert get_bugs_set(mockers) == {0, 123456, 789123} + + +def test_get_jira_set(): + mockers = { + 'job1': {'jira': [0]}, + 'job2': {'jira': ['RHOSINFRA-123']}, + 'job3': {'jira': ['RHOSINFRA-123', 'RHOSENTDFG-456']} + } + assert get_jira_set(mockers) == {0, 'RHOSINFRA-123', 'RHOSENTDFG-456'} + + def test_get_osp_version_func(): assert get_osp_version('DFG-all-unified-16_director-rhel-virthost-3cont_2comp_3ceph-ipv4-geneve-ceph-native-default') == '16' assert get_osp_version('DFG-backup-restore-overcloud-OSP-16-3cont_2comp_3ceph-ipv4-monolithic-broken-node') == '16' @@ -18,6 +36,25 @@ def test_get_osp_version_func(): assert get_osp_version('DFG-all-unified-weekly-multijob') is None +def test_has_blockers(): + mockers = { + 'job1': {'bz': [123456]}, + 'job2': {'jira': ['RHOSINFRA-123']}, + 'job3': {'other': {'name': 'this is a test name'}}, + 'job4': {'bz': [0]}, + 'job5': {'jira': [0]}, + 'job6': {'other': [0]}, + 'job7': {} + } + assert has_blockers(mockers, 'job1') == True + assert has_blockers(mockers, 'job2') == True + assert has_blockers(mockers, 'job3') == True + assert has_blockers(mockers, 'job4') == False + assert has_blockers(mockers, 'job5') == False + assert has_blockers(mockers, 'job6') == False + assert has_blockers(mockers, 'job7') == False + + def test_percent_func(): assert percent(0, 1) == 0.0 assert percent(1, 2) == 50.0 From 0c54e608eb87b0ef1ccf7df5aefa3e8eaa97290b Mon Sep 17 00:00:00 2001 From: Nathan Weinberg Date: Fri, 15 May 2020 13:53:03 -0400 Subject: [PATCH 2/4] Fixed program crashing on malformed BZ/Jira IDs; added some comments --- functions.py | 28 ++++++++++++++++++++++++---- jeeves.py | 6 +++--- report.py | 2 +- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/functions.py b/functions.py index 64b5e82..879bcd1 100644 --- a/functions.py +++ b/functions.py @@ -10,6 +10,8 @@ def generate_header(user, source, remind=False): ''' generates header + if remind is true, header source should be blocker_file + if remind is false, header source should be job_search_fields ''' user_properties = user['property'] user_email_address = [prop['address'] for prop in user_properties if prop['_class'] == 'hudson.tasks.Mailer$UserProperty'][0] @@ -89,8 +91,17 @@ def get_bugs_set(blockers): ''' bug_set = set() for job in blockers: - bz = blockers[job]['bz'] - bug_set.update(bz) + + # try to fetch 'bz' field from job + try: + bz = blockers[job]['bz'] + bug_set.update(bz) + + # failure means data was not formatted correctly for given job - log and skip + except Exception as e: + print("Error getting bug IDs from blockers file for job {}: {}".format(job, e)) + continue + return bug_set @@ -242,8 +253,17 @@ def get_jira_set(blockers): ''' jira_set = set() for job in blockers: - jira = blockers[job]['jira'] - jira_set.update(jira) + + # try to fetch 'jira' field from job + try: + jira = blockers[job]['jira'] + jira_set.update(jira) + + # failure means data was not formatted correctly for given job - log and skip + except Exception as e: + print("Error getting jira IDs from blockers file for job {}: {}".format(job, e)) + continue + return jira_set diff --git a/jeeves.py b/jeeves.py index aaa35da..f07bf96 100755 --- a/jeeves.py +++ b/jeeves.py @@ -29,7 +29,7 @@ save = args.save remind_flag = args.remind - # load configuration data + # load configuration data - if YAML format is invalid, log and end program execution try: with open(config_file, 'r') as file: config = yaml.safe_load(file) @@ -37,7 +37,7 @@ print("Error loading configuration data: ", e) sys.exit() - # load blocker data + # load blocker data - if YAML format is invalid, log and end program execution try: with open(blocker_file, 'r') as file: blockers = yaml.safe_load(file) @@ -45,7 +45,7 @@ print("Error loading blocker configuration data: ", e) sys.exit() - # connect to jenkins server + # connect to jenkins server - if not possible, log and end program execution try: server = jenkins.Jenkins(config['jenkins_url'], username=config['jenkins_username'], password=config['jenkins_api_token']) user = server.get_whoami() diff --git a/report.py b/report.py index 5340494..fc46f94 100644 --- a/report.py +++ b/report.py @@ -13,7 +13,7 @@ def run_report(config, blockers, server, header, test, save): # fetch all relevant jobs jobs = get_jenkins_jobs(server, config['job_search_fields']) - # exit if no jobs found + # log and exit if no jobs found - no reason to send empty report num_jobs_fetched = len(jobs) if num_jobs_fetched == 0: print("No jobs found with given search field. Exiting...") From 15deb91c9dfe4e5c3139d75ba24cd19f34df30d7 Mon Sep 17 00:00:00 2001 From: Nathan Weinberg Date: Sat, 13 Jun 2020 21:20:03 -0400 Subject: [PATCH 3/4] Replaced 'save' run mode with 'no-email' --- docs/README.md | 6 ++--- jeeves.py | 8 +++--- report.py | 66 ++++++++++++++++++++++++++------------------------ 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/docs/README.md b/docs/README.md index cacabc1..a9c315c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -38,12 +38,12 @@ To run: - `$ ./jeeves.py [optional: --config CONFIG] [optional: --blockers BLOCKERS]` if `/usr/bin/python3` is a valid path - `$ python3 jeeves.py [optional: --config CONFIG] [optional: --blockers BLOCKERS]` otherwise - To send report to email specified in `email_to_test` field, add `--test` -- To save report to 'archive' folder, add `--save` +- To only save report to 'archive' folder, and not send an email, add `--no-email` - To run Jeeves in "reminder" mode, add `--remind` - - Note this will override the usage of `--save` and `--test` + - Note this will override the usage of `--no-email` and `--test` #### Reminder Mode -Jeeves has a reminder mode that will send an email to "owners" of jobs in Jenkins that have "UNSTABLE" or "FAILURE" status and have no recorded blockers. +Jeeves has a reminder mode that will send an email to "owners" of jobs in Jenkins that have "UNSTABLE" or "FAILURE" status. You can add as many "owners" as you would like to a given job. You can see some examples of this in "blockers.yaml.example". ### Packages - [PyYAML](https://pyyaml.org/) for parsing config YAML diff --git a/jeeves.py b/jeeves.py index 184bcd3..8a9579e 100755 --- a/jeeves.py +++ b/jeeves.py @@ -20,13 +20,13 @@ parser.add_argument("--config", default="config.yaml", type=str, help='Configuration YAML file to use') parser.add_argument("--blockers", default="blockers.yaml", type=str, help='Blockers YAML file to use') parser.add_argument("--test", default=False, action='store_true', help='Flag to send email to test address') - parser.add_argument("--save", default=False, action='store_true', help='Flag to save report to archives') - parser.add_argument("--remind", default=False, action='store_true', help='Flag to run Jeeves in "reminder" mode. Note this will override --test and --save') + parser.add_argument("--no-email", default=False, action='store_true', help='Flag to not send an email of the report') + parser.add_argument("--remind", default=False, action='store_true', help='Flag to run Jeeves in "reminder" mode. Note this will override --no-email and --save') args = parser.parse_args() config_file = args.config blocker_file = args.blockers test = args.test - save = args.save + no_email = args.no_email remind_flag = args.remind # load configuration data - if YAML format is invalid, log and end program execution @@ -62,4 +62,4 @@ run_remind(config, blockers, server, header) else: header = generate_header(user, config['job_search_fields']) - run_report(config, blockers, server, header, test, save) + run_report(config, blockers, server, header, test, no_email) diff --git a/report.py b/report.py index 248c047..1e77fcd 100644 --- a/report.py +++ b/report.py @@ -8,7 +8,7 @@ get_other_blockers, percent -def run_report(config, blockers, server, header, test, save): +def run_report(config, blockers, server, header, test, no_email): # fetch all relevant jobs jobs = get_jenkins_jobs(server, config['job_search_fields']) @@ -182,42 +182,44 @@ def run_report(config, blockers, server, header, test, save): summary=summary ) - # parse list of email addresses - if test: - recipients = config['email_to_test'].split(',') - else: - recipients = config['email_to'].split(',') + # save HTML report to file + generate_html_file(htmlcode) + print('HTML file generated in "archives" folder') - # construct email - msg = MIMEMultipart() - msg['From'] = header['user_email_address'] - msg['Subject'] = config['email_subject'] - msg['To'] = ", ".join(recipients) - msg.attach(MIMEText(htmlcode, 'html')) + # if "no email" flag has been passed, do not execute this block + if not no_email: + try: - # create SMTP session - if jeeves is unable to do so an HTML file will be generated - try: - with SMTP(config['smtp_host']) as smtp: + # parse list of email addresses + if test: + recipients = config['email_to_test'].split(',') + else: + recipients = config['email_to'].split(',') - # start TLS for security - smtp.starttls() + # construct email + msg = MIMEMultipart() + msg['From'] = header['user_email_address'] + msg['Subject'] = config['email_subject'] + msg['To'] = ", ".join(recipients) + msg.attach(MIMEText(htmlcode, 'html')) - # use ehlo or helo if needed - smtp.ehlo_or_helo_if_needed() + # create SMTP session + with SMTP(config['smtp_host']) as smtp: - # send email to all addresses - response = smtp.sendmail(msg['From'], recipients, msg.as_string()) + # start TLS for security + smtp.starttls() - # log success if all recipients recieved report, otherwise raise exception - if response == {}: - print("Report successfully accepted by mail server for delivery") - else: - raise Exception("Mail server cannot deliver report to following recipients: {}".format(response)) + # use ehlo or helo if needed + smtp.ehlo_or_helo_if_needed() - except Exception as e: - print("Error sending email report: {}\nHTML file generated".format(e)) - generate_html_file(htmlcode) + # send email to all addresses + response = smtp.sendmail(msg['From'], recipients, msg.as_string()) - else: - if save: - generate_html_file(htmlcode) + # log success if all recipients recieved report, otherwise raise exception + if response == {}: + print("Report successfully accepted by mail server for delivery") + else: + raise Exception("Mail server cannot deliver report to following recipients: {}".format(response)) + + except Exception as e: + print('Error sending email report: {}\nSee HTML file saved in "archives" folder'.format(e)) From 6ab15fbfedb4b257107508bf6ea3ce2f47c8ccfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 19:57:35 +0000 Subject: [PATCH 4/4] Bump jira from 3.3.2 to 3.5.0 Bumps [jira](https://github.com/pycontribs/jira) from 3.3.2 to 3.5.0. - [Release notes](https://github.com/pycontribs/jira/releases) - [Commits](https://github.com/pycontribs/jira/compare/3.3.2...3.5.0) --- updated-dependencies: - dependency-name: jira dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cbaf696..9207683 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,5 @@ pyyaml==6.0 jinja2==3.1.2 python-jenkins==1.8.0.0a0 python-bugzilla==3.2.0 -jira==3.3.2 +jira==3.5.0 flake8==5.0.3