diff --git a/.github/workflows/pr_script.yml b/.github/workflows/pr_script.yml new file mode 100644 index 0000000..560077e --- /dev/null +++ b/.github/workflows/pr_script.yml @@ -0,0 +1,39 @@ +name: Run Script on PR +on: + workflow_dispatch: + inputs: + target: + description: Target Pull Request Link + required: true + script: + description: Command(s) to run + required: false + pre_commit: + description: Whether to run the pre-commit script + required: false +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: "3.9" + architecture: "x64" + - name: Upgrade packaging dependencies + run: | + pip install --upgrade pip setuptools wheel --user + - name: Install dependencies + run: | + pip install ghapi pre-commit + - name: Run the script + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} + MAINTAINER: ${{ github.actor }} + TARGET: ${{ github.event.inputs.target }} + SCRIPT: ${{ github.event.inputs.script }} + PRE_COMMIT: ${{ github.event.inputs.pre_commit }} + run: | + python pr_script.py diff --git a/README.md b/README.md index d75891b..3851449 100644 --- a/README.md +++ b/README.md @@ -1 +1,15 @@ # maintainer-tools + +## Workflows + +Workflows for use by maintainers. These should be run from your fork of this repository, with +an [encrypted secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets) called +`ACCESS_TOKEN` that is a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) with `repo` and `workflow` +scopes. + +### PR Script + +The PR Script Workflow allows you to make a commit against a PR as a maintainer without having +to check out the PR locally and push the change. The manual workflow takes as its inputs a link to the PR +and a comma-separated list of quoted commands to run. As a convenience, you can also type "True" for the +option to run pre-commit against the PR to fix up any pre-commit errors. diff --git a/pr_script.py b/pr_script.py new file mode 100644 index 0000000..4b25014 --- /dev/null +++ b/pr_script.py @@ -0,0 +1,83 @@ +import json +import os +from pathlib import Path +import re +import shlex +import shutil +from subprocess import check_output, CalledProcessError, PIPE +import sys + +from ghapi.core import GhApi + + +def run(cmd, **kwargs): + """Run a command as a subprocess and get the output as a string""" + if not kwargs.pop("quiet", False): + print(f"+ {cmd}") + else: + kwargs.setdefault("stderr", PIPE) + + parts = shlex.split(cmd) + if "/" not in parts[0]: + executable = shutil.which(parts[0]) + if not executable: + raise CalledProcessError(1, f'Could not find executable "{parts[0]}"') + parts[0] = executable + + try: + return check_output(parts, **kwargs).decode("utf-8").strip() + except CalledProcessError as e: + print("output:", e.output.decode("utf-8").strip()) + if e.stderr: + print("stderr:", e.stderr.decode("utf-8").strip()) + raise e + + +def run_script(target, script): + """Run a script on the target pull request URL""" + # e.g. https://github.com/foo/bar/pull/81 + owner, repo = target.replace("https://github.com/", "").split('/')[:2] + number = target.split("/")[-1] + auth = os.environ['GITHUB_ACCESS_TOKEN'] + gh = GhApi(owner=owner, repo=repo, token=auth) + # here we get the target owner and branch so we can check it out below + pull = gh.pulls.get(number) + user_name = pull.head.repo.owner.login + branch = pull.head.ref + + if Path("./test").exists(): + shutil.rmtree("./test") + run(f"git clone https://{maintainer}:{auth}@github.com/{user_name}/{repo} -b {branch} test") + os.chdir("test") + run("pip install -e '.[test]'") + for cmd in script: + try: + run(cmd) + except Exception: + continue + + # Use email address for the GitHub Actions bot + # https://github.community/t/github-actions-bot-email-address/17204/6 + run( + 'git config user.email "41898282+github-actions[bot]@users.noreply.github.com"' + ) + run('git config user.name "GitHub Action"') + run(f"git commit -a -m 'Run maintainer script' -m 'by {maintainer}' -m '{json.dumps(script)}'") + run(f"git push origin {branch}") + + +if __name__ == '__main__': + # https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs + target = os.environ.get('TARGET') + maintainer = os.environ['MAINTAINER'] + try: + script = json.loads(os.environ.get('SCRIPT', '[]')) + except Exception: + script = os.environ.get('SCRIPT', []) + if not isinstance(script, list): + script = [script] + if os.environ.get('PRE_COMMIT') == 'true': + script += ['pre-commit run --all-files'] + print(f'Running script on {target}:') + print(f' {script}') + run_script(target, script)