DevOps PR Checker - 10/09/2024
Using python to build fun helper projects at work
DevOps PR Checker
In my current team we always seem to have trouble remembering to review open PRs. I fully understand that this is a company culture and process issue. In theory each team member should check their mail/notifications accordingly. In reality this doesn’t happen and there is no way to actively enforce it in our company or company culture.
To combat this I ended up creating a small Python script just to remind me of open PRs on my name based on a few selected projects. After the creation I added the python script to my .zshrc
file and as an Alias. This way every time a new sessions is started I will be notified of my open PRs with a link to the PR.
So my ZSH alias looks something like this:
# PRO because pull-requests open
alias pro='python c:/sources/custom_tooling/python-cli-pr-checker/pr-checker.py'
The output will then be.
1) project XY | feature: SMIE-30351: new-file-centered-at-origin | https://dev.azure.com/<myfakeorganizationlinkuri>/<id>
1) Project XZ | bugfix: DBTO-34562 removed tab and replaced with spaces | https://dev.azure.com/<myfakeorganizationlinkuri>/<id>
2) Project XZ | chore: DBTO-30123 migrated development workflow in azure pipelines | https://dev.azure.com/<myfakeorganizationlinkuri>/<id>
3) Project XX | chore: TBDO-3452 added .editorconfig for code consistency | https://dev.azure.com/<myfakeorganizationlinkur>/<id>
Simple Implementation
Since I always had a hunch that my teammates would also like to have this project, and not all team members belong to a single project specifically. There had to be some kind of minimal configuration that needs to happen.
How to design the project configuration? I work in the R&D department where everyone has created smaller proof of concepts to production software. So the assumption I made is that we’ve all worked with YAML files before. So I’m choosing that!
The YAML configuration used.
# config.yml
Projects:
- company: my_company # Your company name registerd in DevOps
project_name: XY
repositories:
- XY_project_1
- XY_project_2
- XY_project_3
- XY_project_4
- company: my_company
project_name: XZ
repositories:
- XZ_project_1
- XZ_project_1
- XZ_project_1
Honestly this might seem like a lot of work to setup in the beginning, but it really isn’t and I solved it by pushing the script to its own repository and keeping template files there. This way people can just rename the config template and have a working system in 10 seconds.
So the process would be
- Clone the repository
- Go to the config folder
- Either make your own config as defined above
- Or rename one of the premade configs from
depart_y_template_config.yml
toconfig.yml
- Or rename one of the premade configs from
- Echo a new alias to you
.bashrc
,.zshrc
or whatever shell config file you use - Call the alias
The Python Script
Fair warning I made this script in about 20 minutes, it worked, then I never touched it again. It does one thing, does it very well and doesn’t break. The code can be improved, there is much you can do differently and better. However, its been 3 years, I’ve never had an issue so I won’t be doing any improvements 😊
This is a script that is meant to be used by maybe 15 people, in their dev environment and nothing else.
import os
import yaml
import base64
import requests
from typing import List
from pathlib import Path
from dotenv import load_dotenv
from dataclasses import dataclass
# Becareful not to define anything that shouldn't be overwritten in the .evn
load_dotenv(override=True)
# Constants
CONFIG_PATH = f"{os.path.dirname(os.path.realpath(__file__))}/config/config.yml"
# Environment variables
AZURE_USERNAME = os.getenv("AZURE_USERNAME")
AZURE_TOKEN = os.getenv("AZURE_TOKEN")
@dataclass
class Project:
company: str
project_name: str
repositories: List[str]
@dataclass
class Projects:
items: List[Project]
projects: Projects = []
# File Loading and parsing
with open(Path(CONFIG_PATH)) as stream:
try:
deserialized_projects = yaml.safe_load(stream)
for project in deserialized_projects["Projects"]:
projects.append(Project(**project))
except yaml.YAMLError as exc:
print(exc)
def get_url(company: str, project: str, repo: str) -> str:
url = f"https://dev.azure.com/{company}/{project}/_apis/git/repositories/{repo}/pullrequests?api-version=7.2-preview.2"
return url
def get_pr_url(company, project, repo, id):
return f"https://dev.azure.com/{company}/{project}/_git/{repo}/pullrequest/{id}"
count = 0
azure_project: Project
json_response = "failed request"
for azure_project in projects:
for repository in azure_project.repositories:
authenticated_url = get_url(azure_project.company, azure_project.project_name, repository)
headers = {
"Accept": "application/json",
"Authorization": "Basic " + base64.b64encode((":" + AZURE_TOKEN).encode()).decode(),
}
try:
response = requests.get(authenticated_url, headers=headers)
json_response = response.json()
for idx, val in enumerate(json_response["value"]):
print(
f" {idx+1}) {val['repository']['name']} | {val['title']} | {get_pr_url(azure_project.company, azure_project.project_name, repository, val['pullRequestId'])}")
except Exception as e:
print(e)
if len(json_response["value"]) > 0:
count += json_response['count']
print(f"{count} PR(s) open!")
I love building scripts and software like this. Just make it simple and make it work. No hour long discussion about architecture, no debating on which of the million different frameworks to use. Just basic, use what you know to solve a problem as fast and good as you can and don’t look back. Fun utility projects that benefit everyone. The only package I used as python-dotenv since, I’m working with Azure Tokens to get access to the repositories. But even that isn’t really needed and is just a nicety.
I’m not going to say that now all my problems are fixed, the whole company is using this script or anything because that’s just not the case. There are 5 people including me who use the script. It works. It works well and keeps me up to date with current PRs every time I open my terminal with a direct link tot he PR I need to review. A Successful script for personal use.