Skip to content

Commit

Permalink
GH actions testing
Browse files Browse the repository at this point in the history
  • Loading branch information
GregAC committed May 7, 2023
1 parent 1f9854a commit c861ff1
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 0 deletions.
48 changes: 48 additions & 0 deletions .github/workflows/pr_rtl_change_check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: 'Testing GH Actions'

# **What it does**: Renders the content of every page and check all internal links.
# **Why we have it**: To make sure all links connect correctly.
# **Who does it impact**: Docs content.

on:
workflow_dispatch:
push:
branches:
- main
pull_request:

permissions:
contents: read
# Needed for the 'trilom/file-changes-action' action
pull-requests: read

jobs:
check-links:
runs-on: "ubuntu-latest"
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Install python requirements
run: |
python -m ensurepip --upgrade
audo apt update
sudo apt install -y python3-pip
pip install requests
# Creates file "$/files.json", among others
- name: Gather files changed
uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b
with:
fileOutput: 'json'

# For verification
- name: Show files changed
run: cat $HOME/files.json

- name: Check for blocked changs
run: |
./ci/scripts/check-pr-changes-allowed.py $HOME/files.json \
--gh-repo ${{ github.action_repository }} \
--gh-token ${{ secrets.GITHUB_TOKEN }} \
--pr-ref ${{ github.ref }}
8 changes: 8 additions & 0 deletions BLOCKFILE
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# A test block file

hw/ip/*/rtl/*
hw/ip_templates/*/rtl/*
hw/top_earlgrey/rtl/*
hw/top_earlgrey/ip/*/rtl/*
hw/top_earlgrey/ip_autogen/*/rtl/*

183 changes: 183 additions & 0 deletions ci/scripts/check-pr-changes-allowed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

from fnmatch import fnmatch
import json
import argparse
import sys
import re
import requests

# Number of authorizations required from committers to override a block
NUM_AUTHS_REQD = 2

GH_API_URL = 'https://api.github.com/repos/'

def load_blockfile(blockfile_name):
blocklist = []
with open(blockfile_name, 'r') as blockfile:
for line in blockfile.readlines():
# Remove everything following a '#' for comments
line = line.partition('#')[0]
# Remove leading and trailing whitespace
line = line.strip()

# If anything remains after the above add it to the list
if line != '':
blocklist.append(line)

return blocklist

def load_and_validate_changes(changes_json_file):
with open(changes_json_file, 'r') as changes_json:
changes = json.load(changes_json)

if type(changes) != list:
raise ValueError('Changes JSON must be a list at top-level')

for change in changes:
if type(change) != str:
raise ValueError(f'Saw value {change} in changes JSON which is not '
'a string. Changes JSON must be a single list of '
'strings')

return changes

def check_changes_against_blocklist(changes, blocklist):
blocked_changes = {}

for change in changes:
# Create list of patterns from blocklist that match change
blocked = list(filter(lambda x: x is not None,
map(lambda b: b if fnmatch(change, b) else None, blocklist)))

# If any match patterns exist add them to the blocked_changes dictionary
if blocked:
blocked_changes[change] = blocked

return blocked_changes


pr_ref_re = re.compile(r'refs\/pull\/(\d+)\/merge')
def get_pr_number_from_ref(pr_ref):
pr_ref_match = pr_ref_re.match(pr_ref)

if pr_ref_match:
return int(pr_ref_match.group(1))

raise ValueError(f'{pr_ref} is not a valid PR ref')

def fetch_pr_comments(gh_token, repo_name, pr_number):
headers = {
'Accept': 'application/vnd.github+json',
'Authorization': f'Bearer {gh_token}',
'X-GitHub-Api-Version': '2022-11-28'
}

pr_comment_url = f'{GH_API_URL}{repo_name}/issues/{pr_number}/comments'
pr_comment_request = requests.get(pr_comment_url, headers=headers)

comments_json_list = pr_comment_request.json()
# Return pairs with commenter handle as the first element and the comment
# body as the second
return [(c['user']['login'], c['body']) for c in comments_json_list]

committer_re = re.compile(r'\* ([\w\s-]+) \(([\w-]+)\)')

def load_committers(committers_filename):
with open(committers_filename, 'r') as committers_file:
committer_tuples = committer_re.findall(committers_file.read())

committers_dict = {ct[1].lower(): ct[0] for ct in committer_tuples}

if 'githubid' in committers_dict:
del committers_dict['githubid']

return committers_dict

authorize_re = re.compile(r'^CHANGE AUTHORIZED: (.*)$', re.MULTILINE)

def get_authorized_changes(comments, committers):
file_change_authorizations = {}

for comment_author, comment_body in comments:
comment_author = comment_author.lower()

if comment_author not in committers:
continue

file_authorizations = authorize_re.findall(comment_body)
for filename in file_authorizations:
filename = filename.strip()

if filename not in file_change_authorizations:
file_change_authorizations[filename] = [comment_author]
else:
file_change_authorizations[filename].append(comment_author)

return file_change_authorizations

def main():
arg_parser = argparse.ArgumentParser(
prog='check-pr-changes-allowed',
description='''Checks a list of changed files supplied as a json
list against blocked file patterns. If any match
exits with code 1 and the PR should be blocked.''')

arg_parser.add_argument('changes_json_file', metavar='changes-json-file',
help='''JSON file containing a list of changed files''')

arg_parser.add_argument('--block-file', default='BLOCKFILE',
help='''Plain text file containing path patterns that when matched
indicate a blocked file. One pattern per line''')

arg_parser.add_argument('--pr-ref',
help='git ref of the PR requesting the change')

arg_parser.add_argument('--gh-token',
help='''A github access token used to read PR comments for override
commands''')

arg_parser.add_argument('--gh-repo',
help='Name of the repository on github to read PR comments from',
default='lowrisc/opentitan')

args = arg_parser.parse_args()

blocklist = load_blockfile(args.block_file)
changes = load_and_validate_changes(args.changes_json_file)
blocked_changes = check_changes_against_blocklist(changes, blocklist)

if (args.gh_token is not None and args.pr_ref is not None):
committers = load_committers('COMMITTERS')
pr_number = get_pr_number_from_ref(args.pr_ref)
pr_comments = fetch_pr_comments(args.gh_token, args.gh_repo,
pr_number)
authorized_changes = get_authorized_changes(pr_comments, committers)

block_changes_files = list(blocked_changes.keys())
for change in block_changes_files:
if (change in authorized_changes
and len(authorized_changes[change]) >= NUM_AUTHS_REQD):
del blocked_changes[change]

authorizers = ', '.join([f'{committers[handle]} ({handle})'
for handle in authorized_changes[change]])
print(f'{change} change is authorized by {authorizers}')

if blocked_changes:
for change, block_patterns in blocked_changes.items():
patterns_str = ' '.join(block_patterns)
print(f'{change} blocked by pattern(s): {patterns_str}')

print('UNAUTHORIZED CHANGES PRESENT, PR cannot be merged!')
return 1

print('No unauthorized changes, clear to merge')
return 0

if __name__ == '__main__':
sys.exit(main())

0 comments on commit c861ff1

Please sign in to comment.