From d6ec7a79236930e932e673ed8ebcdb24614a8740 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Wed, 13 Feb 2019 21:07:54 +0100 Subject: [PATCH] first commit --- .gitignore | 2 + .python-version | 1 + .vscode/settings.json | 13 +++ Dockerfile | 18 ++++ LICENSE | 21 +++++ Pipfile | 16 ++++ Pipfile.lock | 182 +++++++++++++++++++++++++++++++++++++++++ README.md | 38 +++++++++ entrypoint.sh | 19 +++++ fixtures/get_file.json | 18 ++++ fixtures/push.json | 135 ++++++++++++++++++++++++++++++ fixtures/release.json | 157 +++++++++++++++++++++++++++++++++++ main.py | 144 ++++++++++++++++++++++++++++++++ requirements.txt | 7 ++ test_main.py | 33 ++++++++ 15 files changed, 804 insertions(+) create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 .vscode/settings.json create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 README.md create mode 100755 entrypoint.sh create mode 100644 fixtures/get_file.json create mode 100644 fixtures/push.json create mode 100644 fixtures/release.json create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 test_main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..689264b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +.pytest_cache diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..a76ccff --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.7.1 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..24c4ce1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "python.linting.pylintEnabled": false, + "python.linting.flake8Enabled": true, + "python.linting.enabled": true, + "python.pythonPath": "/home/dz0ny/.local/share/virtualenvs/make-pr-9Hm3Atzw/bin/python", + "python.formatting.provider": "black", + "python.unitTest.pyTestArgs": [ + "." + ], + "python.unitTest.unittestEnabled": false, + "python.unitTest.nosetestsEnabled": false, + "python.unitTest.pyTestEnabled": true +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..442cf58 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.7.2-alpine3.9 + +LABEL version="1.0.0" +LABEL repository="http://github.com/dz0ny/make-pr" +LABEL homepage="http://github.com/dz0ny/make-pr" +LABEL maintainer="Janez Troha" +LABEL "com.github.actions.name"="Automatic PR" +LABEL "com.github.actions.description"="Automatically creates PR" +LABEL "com.github.actions.icon"="git-pull-request" +LABEL "com.github.actions.color"="purple" + +RUN apk --no-cache add jq bash +ADD requirements.txt /requirements.txt +RUN pip install -r /requirements.txt && rm -f /requirements.txt + +ADD main.py /main.py +ADD entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1fbd72d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Janez Troha + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..e7e417f --- /dev/null +++ b/Pipfile @@ -0,0 +1,16 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +flake8 = "*" +black = "==18.9b0" +pytest = "*" + +[packages] +requests = "*" +dacite = "*" + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..f69d3e4 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,182 @@ +{ + "_meta": { + "hash": { + "sha256": "103eadb4e274b7f688c7b8b6c478de2816da35f7fb1ffe4908bafb88a7f9ef05" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + ], + "version": "==2018.11.29" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "dacite": { + "hashes": [ + "sha256:960265775c0a360f9ecd7f3b9db7d86a5f1b7b97427e443c71ea1568976ba1b9", + "sha256:bf06d726e20746cb5fbcc638aa42243ae4c4d1b5874558d3a03395423b94e78f" + ], + "index": "pypi", + "version": "==0.0.23" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "index": "pypi", + "version": "==2.21.0" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "version": "==1.24.1" + } + }, + "develop": { + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "version": "==1.4.3" + }, + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, + "black": { + "hashes": [ + "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", + "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5" + ], + "index": "pypi", + "version": "==18.9b0" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "version": "==7.0" + }, + "entrypoints": { + "hashes": [ + "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", + "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + ], + "version": "==0.3" + }, + "flake8": { + "hashes": [ + "sha256:c3ba1e130c813191db95c431a18cb4d20a468e98af7a77e2181b68574481ad36", + "sha256:fd9ddf503110bf3d8b1d270e8c673aab29ccb3dd6abf29bae1f54e5116ab4a91" + ], + "index": "pypi", + "version": "==3.7.5" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "more-itertools": { + "hashes": [ + "sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40", + "sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1" + ], + "markers": "python_version > '2.7'", + "version": "==6.0.0" + }, + "pluggy": { + "hashes": [ + "sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616", + "sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a" + ], + "version": "==0.8.1" + }, + "py": { + "hashes": [ + "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", + "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" + ], + "version": "==1.7.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + ], + "version": "==2.5.0" + }, + "pyflakes": { + "hashes": [ + "sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d", + "sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd" + ], + "version": "==2.1.0" + }, + "pytest": { + "hashes": [ + "sha256:80cfd9c8b9e93f419abcc0400e9f595974a98e44b6863a77d3e1039961bfc9c4", + "sha256:c2396a15726218a2dfef480861c4ba37bd3952ebaaa5b0fede3fc23fddcd7f8c" + ], + "index": "pypi", + "version": "==4.2.1" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..37a6c38 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# GitHub action to automatically create PRs + +## Installation + +To configure the action simply add the following lines to your `.github/main.workflow` workflow file: + +``` +workflow "Automatic PR" { + on = "release" + resolves = "Create PR" +} + +action "Create PR" { + uses = "dz0ny/create-pr:master" + args = "path/to/my/patch_recipe.py" + secrets = ["GITHUB_TOKEN"] +} +``` + +Contents of `path/to/my/patch_recipe.py: + +```python +if __name__ == "__main__": + event = Event.fromPath(env["GITHUB_EVENT_PATH"]) + if not event.is_release: + raise Exception("This event is not from release") + + with commit(event, f"update_{event.release.tag_name}") as gh: + versions = gh.get("bin/runtime/versions") + versions.text: str = versions.text.replace("foo.bar", "def.bar") + gh.add(versions, "Update image to latest version") + gh.create_pr( + "Update image to latest version", + "This updates web image to the latest released version", + ) + ``` + +check api in [main.py](main.py). diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..d96c8df --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +# Ensure that the GITHUB_TOKEN secret is included +if [[ -z "$GITHUB_TOKEN" ]]; then + echo "Set the GITHUB_TOKEN env variable." + exit 1 +fi + +# Ensure that the file path is present +if [[ -z "$1" ]]; then + echo "You must pass at least one argument to this action, the path to the patching recepie." + exit 1 +fi +echo "Appending patch file $1 to main" +cat $1 >> /main.py +echo "Run patch file in main" +python /main.py diff --git a/fixtures/get_file.json b/fixtures/get_file.json new file mode 100644 index 0000000..5d1183f --- /dev/null +++ b/fixtures/get_file.json @@ -0,0 +1,18 @@ +{ + "type": "file", + "encoding": "base64", + "size": 5362, + "name": "README.md", + "path": "README.md", + "content": "bXkgdXBkYXRlZCBmaWxlIGNvbnRlbnRz", + "sha": "3d21ec53a331a6f037a91c368710b99387d012c1", + "url": "https://api.github.com/repos/octokit/octokit.rb/contents/README.md", + "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", + "html_url": "https://github.com/octokit/octokit.rb/blob/master/README.md", + "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/README.md", + "_links": { + "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1", + "self": "https://api.github.com/repos/octokit/octokit.rb/contents/README.md", + "html": "https://github.com/octokit/octokit.rb/blob/master/README.md" + } + } diff --git a/fixtures/push.json b/fixtures/push.json new file mode 100644 index 0000000..2d388c1 --- /dev/null +++ b/fixtures/push.json @@ -0,0 +1,135 @@ +{ + "ref": "refs/tags/simple-tag", + "before": "a10867b14bb761a232cd80139fbd4c0d33264240", + "after": "0000000000000000000000000000000000000000", + "created": false, + "deleted": true, + "forced": false, + "base_ref": null, + "compare": "https://github.com/Codertocat/Hello-World/compare/a10867b14bb7...000000000000", + "commits": [ + + ], + "head_commit": null, + "repository": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "name": "Codertocat", + "email": "21031067+Codertocat@users.noreply.github.com", + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": 1527711484, + "updated_at": "2018-05-30T20:18:35Z", + "pushed_at": 1527711528, + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 2, + "license": null, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master", + "stargazers": 0, + "master_branch": "master" + }, + "pusher": { + "name": "Codertocat", + "email": "21031067+Codertocat@users.noreply.github.com" + }, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + } + } diff --git a/fixtures/release.json b/fixtures/release.json new file mode 100644 index 0000000..162919c --- /dev/null +++ b/fixtures/release.json @@ -0,0 +1,157 @@ +{ + "action": "published", + "release": { + "url": "https://api.github.com/repos/Codertocat/Hello-World/releases/11248810", + "assets_url": "https://api.github.com/repos/Codertocat/Hello-World/releases/11248810/assets", + "upload_url": "https://uploads.github.com/repos/Codertocat/Hello-World/releases/11248810/assets{?name,label}", + "html_url": "https://github.com/Codertocat/Hello-World/releases/tag/0.0.1", + "id": 11248810, + "node_id": "MDc6UmVsZWFzZTExMjQ4ODEw", + "tag_name": "0.0.1", + "target_commitish": "master", + "name": null, + "draft": false, + "author": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "prerelease": false, + "created_at": "2018-05-30T20:18:05Z", + "published_at": "2018-05-30T20:18:44Z", + "assets": [ + + ], + "tarball_url": "https://api.github.com/repos/Codertocat/Hello-World/tarball/0.0.1", + "zipball_url": "https://api.github.com/repos/Codertocat/Hello-World/zipball/0.0.1", + "body": null + }, + "repository": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2018-05-30T20:18:04Z", + "updated_at": "2018-05-30T20:18:35Z", + "pushed_at": "2018-05-30T20:18:44Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 2, + "license": null, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + } + } diff --git a/main.py b/main.py new file mode 100644 index 0000000..0131aef --- /dev/null +++ b/main.py @@ -0,0 +1,144 @@ +import requests +from os import environ as env +from contextlib import contextmanager +from dataclasses import dataclass +from pathlib import Path +import json +from dacite import from_dict +import typing as t +from base64 import b64encode, b64decode + + +@dataclass(frozen=True) +class Sender: + type: str # "User" + login: str # "Codertocat" + id: int # 21031067 + url: str # "https://api.github.com/users/Codertocat" + + +@dataclass(frozen=True) +class Release: + url: str # "https://api.github.com/repos/Codertocat/Hello-World/releases/11248810" + html_url: str # "https://github.com/Codertocat/Hello-World/releases/tag/0.0.1" + id: int # 11248810 + node_id: str # "MDc6UmVsZWFzZTExMjQ4ODEw" + tag_name: str # "0.0.1" + target_commitish: str # "master" + created_at: str # "2018-05-30T20:18:05Z" + published_at: str # "2018-05-30T20:18:44Z" + + +@dataclass(frozen=True) +class Repository: + id: int + name: str # "Hello-World" + full_name: str # "Codertocat/Hello-World" + private: bool # false + description: t.Optional[str] # "Hello-World Repo" + fork: bool # false + url: str # "https://api.github.com/repos/Codertocat/Hello-World" + default_branch: str # "master" + + +@dataclass(frozen=True) +class Event: + repository: Repository + sender: Sender + release: t.Optional[Release] + + @classmethod + def fromPath(cls, event_path: str) -> "Event": + text = Path(event_path).read_text() + data = json.loads(text) + return from_dict(cls, data) + + @property + def is_release(self) -> bool: + return bool(self.release) + + @property + def base(self) -> str: + return self.repository.url + + +@dataclass +class File: + type: str # "file" + encoding: str # "base64" + size: int # 5362 + name: str # "README.md" + path: str # "README.md" + content: str # "encoded content ..." + sha: str # "3d21ec53a331a6f037a91c368710b99387d012c1" + url: str # "https://api.github.com/repos/octokit/octokit.rb/contents/README.md" + + def patch(self, message: str) -> dict: + return {"message": message, "content": File.content, "sha": File.sha} + + @property + def text(self) -> str: + return b64decode(self.content).decode("utf8") + + @text.setter + def text(self, data: str): + self.content = b64encode(data.encode("utf8")) + + @classmethod + def fromJSON(cls, data: dict) -> "File": + return from_dict(cls, data) + + +class GitHub: + + http: requests.Session + + def __init__(self, event: Event, branch: str, github_token: str): + self.http = requests.Session() + self.http.headers["Authorization"] = f"Token {github_token}" + self.http.headers["Accept"] = "application/vnd.github.v3+json" + self.branch = branch + self.event = event + + def get(self, repo: str, path: str) -> File: + res = self.http.get(f"https://api.github.com/repos/{repo}/contents/{path}") + res.raise_for_status() + return File.fromJSON(res.json()) + + def create_pr(self, title: str, body: str): + res = self.http.post( + f"{self.event.base}/pulls", + json={"title": title, "body": body, "head": self.branch, "base": "master"}, + ) + res.raise_for_status() + return res.json() + + def add(self, file: File, message: str): + res = self.http.put(file.url, json=file.patch) + res.raise_for_status() + return res.json() + + +@contextmanager +def commit(event: Event, new_branch: str) -> "GitHub": + gh: GitHub = GitHub(event, new_branch, env["PTA_TOKEN"]) + yield gh + + +# Example + +""" +if __name__ == "__main__": + event = Event.fromPath(env["GITHUB_EVENT_PATH"]) + if not event.is_release: + raise Exception("This event is not from release") + + with commit(event, f"update_{event.release.tag_name}") as gh: + versions = gh.get("bin/runtime/versions") + versions.text: str = versions.text.replace("foo.bar", "def.bar") + gh.add(versions, "Update image to latest version") + gh.create_pr( + "Update image to latest version", + "This updates web image to the latest released version", + ) +""" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8c14368 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +-i https://pypi.org/simple +certifi==2018.11.29 +chardet==3.0.4 +dacite==0.0.23 +idna==2.8 +requests==2.21.0 +urllib3==1.24.1 diff --git a/test_main.py b/test_main.py new file mode 100644 index 0000000..8441436 --- /dev/null +++ b/test_main.py @@ -0,0 +1,33 @@ +from main import Event +from main import File +from pathlib import Path +import json + + +def test_json_release(): + ev: Event = Event.fromPath("fixtures/release.json") + assert ev.repository.name == "Hello-World" + assert ev.is_release + assert ev.base == "https://api.github.com/repos/Codertocat/Hello-World" + + +def test_json_push(): + ev: Event = Event.fromPath("fixtures/push.json") + assert ev.repository.name == "Hello-World" + assert not ev.is_release + assert ev.base == "https://api.github.com/repos/Codertocat/Hello-World" + + +def test_file_decode(): + text = Path("fixtures/get_file.json").read_text() + data = json.loads(text) + ev: File = File.fromJSON(data) + assert ev.text == "my updated file contents" + + +def test_file_encode(): + text = Path("fixtures/get_file.json").read_text() + data = json.loads(text) + ev: File = File.fromJSON(data) + ev.text = "my new file contents" + assert ev.content == b"bXkgbmV3IGZpbGUgY29udGVudHM="