diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff7ef29 --- /dev/null +++ b/.gitignore @@ -0,0 +1,169 @@ +# lambda +package +function.zip + + + + + +# Created by https://www.toptal.com/developers/gitignore/api/vim,python +# Edit at https://www.toptal.com/developers/gitignore?templates=vim,python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# End of https://www.toptal.com/developers/gitignore/api/vim,python diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..19daad4 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +.PHONY: all +all: lambda + +.PHONY: requirements +requirements: requirements-to-freeze.txt + @python3 -c 'print("Updating requirements.txt...")' + @docker run --rm -v $$(pwd):/usr/src/app/ python:3.8 /bin/bash -c "python -m pip install --upgrade pip && pip install -r /usr/src/app/requirements-to-freeze.txt && pip freeze > /usr/src/app/requirements.txt" + +.PHONY: clean +clean: clean-python + +.PHONY: clean-python +clean-python: + @# Prepending each recipe with - to continue regardless of errors per + @# https://www.gnu.org/software/make/manual/html_node/Errors.html + @-find . -type d -name '__pycache__' -exec rm -rf {} + + @-find . -type d -name '.mypy_cache' -exec rm -rf {} + + @-find . -type d -name '.pytest_cache' -exec rm -rf {} + + @-find . -type f -name '*.pyc' -delete + +.PHONY: lambda +lambda: clean + @docker run --rm -v $$(pwd):/usr/src/app/ python:3.8 /bin/bash -c "cd /usr/src/app/ && apt-get update && apt-get -y --no-install-recommends install zip && python -m pip install --upgrade pip && zip function.zip lambda_function.py && pip install --target ./package -r requirements.txt && cd package && zip -r9 ../function.zip ." + @echo "docker run --rm -it --env-file <(env | grep AWS_) -v $$(pwd):/usr/src/app/ -v ${HOME}/.aws:/root/.aws easy_infra aws lambda update-function-code --function-name release_monitor --zip-file fileb:///usr/src/app/function.zip" diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd091f6 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Release Monitor + +This project uses the unauthenticated GitHub APIs to monitor a provided account/repo for a release containing a provided commit. It is meant to be run as an AWS lambda on a cron, and send an email notification when a release with the provided commit is detected. + +## Quickstart +1. Create a lambda in your AWS account. +1. Auth to AWS via environment variables. +1. Run the following: +```bash +make +docker run --rm -it --env-file <(env | grep AWS_) -v $(pwd):/usr/src/app/ -v ${HOME}/.aws:/root/.aws seiso/easy_infra aws lambda update-function-code --function-name release_monitor --zip-file fileb:///usr/src/app/function.zip +``` + diff --git a/lambda_function.py b/lambda_function.py new file mode 100755 index 0000000..56a0b18 --- /dev/null +++ b/lambda_function.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Search GitHub to see if a given commit is in a release +""" + +from typing import Dict +import sys +from argparse import ArgumentParser +import requests + +def lambda_handler(event, context): + """ + AWS Lambda handler + """ + check_for_commit(account=event["account"], repository=event["repository"], commitish=event["commit"]) + + +def main(): + """ + Retrieve the arguments and check for the provided commit in the latest release + """ + config = get_args_config() + + check_for_commit(account=config["account"], repository=config["repository"], commitish=config["commit"]) + + +def check_for_commit(*, account: str, repository: str, commitish: str) -> bool: + """ + Check a provided account/repo for a commitish + """ + url = "https://api.github.com/repos/" + account + "/" + repository + "/releases/latest" + headers = {"Accept": "application/vnd.github.v3+json"} + + response = requests.get(url, headers=headers) + latest_release_json = response.json() + try: + latest_release_sha = latest_release_json["target_commitish"] + except KeyError: + print("Unable to identify the latest release commitish, are you being rate limited?") + sys.exit(1) + + url = "https://api.github.com/repos/" + account + "/" + repository + "/compare/" + commitish + "..." + latest_release_sha + + response = requests.get(url, headers=headers) + github_status_json = response.json() + try: + github_status = github_status_json["status"] + except KeyError: + print("Unable to identify the comparison status, are you being rate limited?") + sys.exit(1) + + if github_status in ("ahead", "identical"): + print("YES! Go update") + return True + + print("not yet ;(") + return False + + +def get_args_config() -> Dict: + """ + Get the configs passed as arguments + """ + parser = create_arg_parser() + return vars(parser.parse_args()) + + +def create_arg_parser() -> ArgumentParser: + """Parse the arguments""" + parser = ArgumentParser() + parser.add_argument( + "--account", type=str, required=True, help="github account", + ) + parser.add_argument( + "--repository", type=str, required=True, help="github repository", + ) + parser.add_argument( + "--commit", type=str, required=True, help="commitish to monitor for", + ) + return parser + +if __name__ == "__main__": + main() diff --git a/requirements-to-freeze.txt b/requirements-to-freeze.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements-to-freeze.txt @@ -0,0 +1 @@ +requests diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ef0c170 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +certifi==2020.6.20 +chardet==3.0.4 +idna==2.10 +requests==2.24.0 +urllib3==1.25.10