Skip to content

Commit 26979c5

Browse files
authored
Merge pull request #10 from Qalthos/make_this_a_tool
Make this a package alternate approach
2 parents 2539935 + b404932 commit 26979c5

File tree

9 files changed

+174
-134
lines changed

9 files changed

+174
-134
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
.vscode/
33
*.code-workspace
44
__pycache__/
5+
*.egg-info

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Ansible Network Triager
44

5-
Set repositories of interest and run with `python -m triager -c /path/to/config.yaml`
5+
Set repositories of interest and run with `triager -c /path/to/config.yaml`
66

77
This tool assists in weekly bug triages by fetching all issues and pull-requests
88
from repositories specified in the config file that were created (or updated)
@@ -13,6 +13,10 @@ currently unassigned are pulled.
1313
By default, this prints out a table built from the fetched content to the console.
1414
When run with `--send-email` it also emails this table to all the listed maintainers.
1515

16+
## Installation
17+
18+
pip install git+https://github.com/ansible-network/ansible-network-triager.git@master
19+
1620
## Usage
1721
Options | Usage
1822
--- | ---
@@ -25,6 +29,7 @@ Options | Usage
2529
## Notes
2630
- An example config file (example-config.yaml) has been placed in this repository for reference.
2731
- Tested with Python 3.6
32+
- This tool gets installed as a part of `ansible-network-tools` package.
2833

2934
## Licensing
3035

setup.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from setuptools import setup
2+
3+
setup(
4+
name="ansible-network-tools",
5+
version="1.0.0",
6+
description="A set of utility tools for the Ansible Network Team",
7+
url="http://github.com/ansible-network/ansible-network-triager",
8+
author="Ansible Network Team",
9+
license="GPLv3",
10+
packages=["triager"],
11+
install_requires=["pTable", "requests", "PyYAML"],
12+
entry_points={"console_scripts": ["triager=triager.__main__:main"]},
13+
zip_safe=False,
14+
)

triager/__init__.py

Whitespace-only changes.

triager/__main__.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
import logging
33
from datetime import datetime
44

5+
from triager import triager
6+
from triager.config import Config
57
from triager.mailer import send_mail
8+
from triager.release import __ver__
69
from triager.tablemaker import make_table
7-
from triager.triager import Triager
810

911

1012
def run(args):
@@ -17,23 +19,19 @@ def run(args):
1719
format="%(levelname)-10s%(message)s", level=logging_level
1820
)
1921

20-
triager = Triager(cfg=args.config_file)
21-
issues = triager.triage()
22+
config = Config(args.config_file)
23+
issues = triager.triage(config)
2224

2325
if issues:
2426
table = make_table(issues)
2527
logging.info("Printing triaged table to console")
2628
print(table)
2729

28-
if args.send_email is True:
29-
send_mail(
30-
content=table,
31-
sender=triager.sender,
32-
receivers=triager.maintainers,
33-
)
30+
if args.send_email is True and config.is_email_ready:
31+
send_mail(content=table, config=config)
3432

3533

36-
if __name__ == "__main__":
34+
def main():
3735
parser = argparse.ArgumentParser(
3836
description="Triage issues and pull-requests from repositories of interest.",
3937
prog="Ansible Network Triager",
@@ -71,4 +69,17 @@ def run(args):
7169
help="send the triaged table as an email to the list of maintainers",
7270
)
7371

74-
run(parser.parse_args())
72+
group.add_argument(
73+
"--version", action="store_true", help="show version number",
74+
)
75+
76+
args = parser.parse_args()
77+
78+
if args.version:
79+
print("Ansible Network Triager, version {0}".format(__ver__))
80+
else:
81+
run(args)
82+
83+
84+
if __name__ == "__main__":
85+
main()

triager/config.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import logging
2+
import os
3+
import sys
4+
from datetime import datetime, timedelta
5+
from email.headerregistry import Address
6+
7+
import yaml
8+
9+
10+
class Config:
11+
def __init__(self, cfg):
12+
# select config.yaml from cwd
13+
if not cfg:
14+
logging.info("config file not specified, setting default")
15+
cfg = "./config.yaml"
16+
17+
logging.info("attempting to read config file: {0}".format(cfg))
18+
19+
try:
20+
with open(cfg, "r") as config_file:
21+
config = yaml.safe_load(config_file)
22+
logging.info("config file successfully loaded")
23+
except FileNotFoundError as e:
24+
logging.critical(e)
25+
sys.exit()
26+
27+
logging.info("parsing information from config file")
28+
29+
# Populate org and repos to triage
30+
self.repos = []
31+
logging.debug("parsing orgs and repositories from config file")
32+
for org in config["orgs"]:
33+
for repo in org["repos"]:
34+
self.repos.append((org["name"], repo))
35+
36+
# Populate maintainers list
37+
logging.debug("parsing list of maintainers from config file")
38+
self.maintainers = [
39+
Address(item["name"], addr_spec=item["email"])
40+
for item in config.get("maintainers", [])
41+
]
42+
43+
# Set address to send triage emails from
44+
logging.debug("parsing triager email and password from config file")
45+
self.sender = None
46+
if "triager" in config:
47+
try:
48+
self.sender = {
49+
"email": config["triager"]["address"],
50+
"password": config["triager"]["password"],
51+
}
52+
except KeyError as exc:
53+
logging.error(f"triager config malformed, key {exc!s} not found")
54+
except TypeError:
55+
logging.error("triager config malformed, should be a dictionary")
56+
else:
57+
logging.debug("triager not found in config, will not send email")
58+
59+
# Set last triage date
60+
logging.debug("setting last triage date")
61+
self.last_triage_date = datetime.utcnow() - timedelta(
62+
days=int(config["timedelta"])
63+
)
64+
65+
logging.info("config file successfully parsed")
66+
67+
@property
68+
def token(self):
69+
logging.debug("fetching oauth token")
70+
if os.getenv("GH_TOKEN"):
71+
return {"Authorization": "token {0}".format(os.getenv("GH_TOKEN"))}
72+
73+
@property
74+
def is_email_ready(self):
75+
return bool(self.sender and self.maintainers)

triager/mailer.py

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,30 @@
11
import logging
22
import smtplib
33
from datetime import date
4-
from email.headerregistry import Address
54
from email.message import EmailMessage
65

76

8-
def send_mail(content, sender, receivers=[]):
7+
def send_mail(content, config):
98
logging.info("attempting to send email to maintainers")
109

11-
if not sender.get("email") or not sender.get("password"):
12-
logging.critical(
13-
"Triager email or password missing from config file, email could not be sent"
14-
)
15-
else:
16-
msg = EmailMessage()
17-
msg["From"] = sender["email"]
18-
msg["To"] = _get_recipients(receivers)
19-
msg["Subject"] = "Ansible Network Weekly Triage - {0}".format(
20-
date.today().isoformat()
21-
)
22-
msg.set_content(str(content))
23-
msg.add_alternative(
24-
content.get_html_string(
25-
attributes={"border": 1, "style": "text-align:center"}
26-
),
27-
subtype="html",
28-
)
10+
msg = EmailMessage()
11+
msg["From"] = config.sender["email"]
12+
msg["To"] = config.maintainers
13+
msg["Subject"] = "Ansible Network Weekly Triage - {0}".format(
14+
date.today().isoformat()
15+
)
16+
msg.set_content(str(content))
17+
msg.add_alternative(
18+
content.get_html_string(
19+
attributes={"border": 1, "style": "text-align:center"}
20+
),
21+
subtype="html",
22+
)
2923

30-
with smtplib.SMTP("smtp.gmail.com", 587) as smtp:
31-
logging.info("attempting to send email")
32-
smtp.starttls()
33-
smtp.login(sender["email"], sender["password"])
34-
smtp.send_message(msg)
24+
with smtplib.SMTP("smtp.gmail.com", 587) as smtp:
25+
logging.info("attempting to send email")
26+
smtp.starttls()
27+
smtp.login(config.sender["email"], config.sender["password"])
28+
smtp.send_message(msg)
3529

36-
logging.info("email sent successfully")
37-
38-
39-
def _get_recipients(receivers):
40-
logging.info("generating list of receipients")
41-
return [
42-
Address(item["name"], addr_spec=item["email"]) for item in receivers
43-
]
30+
logging.info("email sent successfully")

triager/release.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__ver__ = "1.0.0"
2+
__author__ = "Ansible Network Team"

triager/triager.py

Lines changed: 34 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,48 @@
11
import logging
2-
import os
3-
import sys
4-
from datetime import datetime, timedelta
52

63
import requests
7-
import yaml
84

9-
REQUEST_FMT = "https://api.github.com/repos/{0}/{1}/issues"
10-
11-
12-
class Triager:
13-
def __init__(self, cfg):
14-
self.oauth_token = os.getenv("GH_TOKEN")
15-
16-
# select config.yaml from cwd
17-
if not cfg:
18-
logging.info("config file not specified, setting default")
19-
cfg = "./config.yaml"
205

21-
logging.info("attempting to read config file: {0}".format(cfg))
22-
23-
try:
24-
with open(cfg, "r") as config_file:
25-
config = yaml.safe_load(config_file)
26-
logging.info("config file successfully loaded")
27-
except FileNotFoundError as e:
28-
logging.critical(e)
29-
sys.exit()
6+
REQUEST_FMT = "https://api.github.com/repos/{0}/{1}/issues"
307

31-
logging.info("parsing information from config file")
328

33-
# Populate org and repos to triage
34-
self.repos = []
35-
logging.debug("parsing orgs and repositories from config file")
36-
for org in config["orgs"]:
37-
for repo in org["repos"]:
38-
self.repos.append((org["name"], repo))
9+
def triage(config):
10+
issues = {}
11+
for org, repo in config.repos:
12+
repo_name = repo["name"]
13+
repo_labels = repo.get("labels", [])
3914

40-
# Populate maintainers list
41-
logging.debug("parsing list of maintainers from config file")
42-
self.maintainers = config["maintainers"]
15+
params = dict(since=config.last_triage_date.isoformat())
16+
if repo_labels:
17+
params["labels"] = ",".join(repo_labels)
18+
else:
19+
params["assignee"] = "none"
4320

44-
# Set address to send triage emails from
45-
logging.debug("parsing triager email and password from config file")
46-
self.sender = {
47-
"email": config.get("triager", {}).get("address"),
48-
"password": config.get("triager", {}).get("password"),
49-
}
21+
issues[repo_name] = []
5022

51-
# Set last triage date
52-
logging.debug("setting last triage date")
53-
self.last_triage_date = datetime.utcnow() - timedelta(
54-
days=int(config["timedelta"])
23+
logging.info(
24+
"requesting issue details for {0}/{1}".format(org, repo_name)
25+
)
26+
resp = requests.get(
27+
REQUEST_FMT.format(org, repo_name),
28+
params=params,
29+
headers=config.token,
5530
)
5631

57-
logging.info("config file successfully parsed")
58-
59-
def triage(self):
60-
issues = {}
61-
for org, repo in self.repos:
62-
repo_name = repo["name"]
63-
repo_labels = repo.get("labels", [])
64-
65-
params = dict(since=self.last_triage_date.isoformat())
66-
if repo_labels:
67-
params["labels"] = ",".join(repo_labels)
68-
else:
69-
params["assignee"] = "none"
70-
71-
issues[repo_name] = []
72-
73-
logging.info(
74-
"requesting issue details for {0}/{1}".format(org, repo_name)
75-
)
76-
resp = requests.get(
77-
REQUEST_FMT.format(org, repo_name),
78-
params=params,
79-
headers=self._get_token(),
32+
if not resp.ok:
33+
logging.critical(resp.json()["message"])
34+
return {}
35+
36+
for item in resp.json():
37+
issues[repo_name].append(
38+
{
39+
"url": item["html_url"],
40+
"title": item["title"],
41+
"type": "Pull Request"
42+
if item.get("pull_request")
43+
else "Issue",
44+
}
8045
)
8146

82-
if not resp.ok:
83-
logging.critical(resp.json()["message"])
84-
return {}
85-
86-
for item in resp.json():
87-
issues[repo_name].append(
88-
{
89-
"url": item["html_url"],
90-
"title": item["title"],
91-
"type": "Pull Request"
92-
if item.get("pull_request")
93-
else "Issue",
94-
}
95-
)
96-
97-
logging.info("triage successfully completed")
98-
return issues
99-
100-
def _get_token(self):
101-
logging.debug("fetching oauth token")
102-
if self.oauth_token:
103-
return {"Authorization": "token {0}".format(self.oauth_token)}
47+
logging.info("triage successfully completed")
48+
return issues

0 commit comments

Comments
 (0)