commit c9d3643b20e96ab5d67d781b0071fdafa295f0bc Author: eine Date: Thu May 14 16:58:15 2020 +0200 initial commit diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..b0ebc6e --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,29 @@ +name: 'tip' + +on: + push: + tags: + - '*' + - '!tip' + branches: + - '*' + pull_request: + +env: + CI: true + +jobs: + + tip: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: | + echo "Build some tool and generate some artifacts" > artifact.txt + - uses: ./ + with: + token: ${{ secrets.GITHUB_TOKEN }} + files: | + artifact.txt + README.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..82ca70d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM python:alpine +COPY tip.py /tip.py +RUN pip install PyGithub --progress-bar off +ENTRYPOINT ["/tip.py"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb12346 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +**tip** is a Docker GitHub Action written in Python. **tip** allows to keep a pre-release and its artifacts up to date with a latest builds. Combined with a workflow that is executed periodically, **tip** allows to provide a fixed release name for users willing to use daily/nightly artifacts of a project. + +The following block shows a minimal YAML workflow file: + +```yml +name: 'workflow' + +on: + schedule: + - cron: '0 0 * * 5' + +jobs: + mwe: + runs-on: ubuntu-latest + steps: + + # Clone repository + - uses: actions/checkout@v2 + + # Build your application, tool, artifacts, etc. + - name: Build + run: | + echo "Build some tool and generate some artifacts" > artifact.txt + + # Update tag and pre-release + # - Update (force-push) tag to the commit that is used in the workflow. + # - Upload artifacts defined by the user. + - uses: eine/tip@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + files: | + artifact.txt + README.md +``` + +Note that the tag and the pre-release need to be created manually the first time. The workflow above will fail if the release does not exist. + +The default tag name is `tip`, but it can be optionally overriden through option `tag` or setting envvar `INPUT_TAG`. diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..ec6b715 --- /dev/null +++ b/action.yml @@ -0,0 +1,16 @@ +name: 'tip' +description: "keep a pre-release always up-to-date" +inputs: + token: + description: 'Token to make authenticated API calls; can be passed in using {{ secrets.GITHUB_TOKEN }}' + required: true + files: + description: 'Multi-line list of glob patterns describing the artifacts to be uploaded' + required: true + tag: + description: 'Name of the tag that corresponds to the tip/nightly pre-release' + required: false + default: tip +runs: + using: 'docker' + image: 'Dockerfile' diff --git a/tip.py b/tip.py new file mode 100755 index 0000000..92c24ee --- /dev/null +++ b/tip.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 + +from os import environ, getenv +from sys import argv, stdout +from github import Github +from subprocess import check_call +from glob import glob + +print("· Get list of artifacts to be uploaded") + +args = [] +files = [] + +if 'INPUT_FILES' in environ: + args = environ['INPUT_FILES'].split() + +if len(argv) > 1: + args = args + argv[1:] + +if len(args) == 0: + stdout.flush() + raise(Exception("Glob patterns need to be provided as positional arguments or through envvar 'INPUT_FILES'!")) + +for item in args: + items = glob(item) + print("glob(%s)" % item, "->", items) + files = files + items + +if len(files) < 1: + stdout.flush() + raise(Exception('Empty list of files to upload/update!')) + +print("· Get GitHub API handler (authenticate)") + +if 'GITHUB_TOKEN' in environ: + gh = Github(environ["GITHUB_TOKEN"]) +elif 'INPUT_TOKEN' in environ: + gh = Github(environ["INPUT_TOKEN"]) +else: + if 'GITHUB_USER' not in environ or 'GITHUB_PASS' not in environ: + stdout.flush() + raise(Exception("Need credentials to authenticate! Please, provide 'GITHUB_TOKEN', 'INPUT_TOKEN', or 'GITHUB_USER' and 'GITHUB_PASS'")) + gh = Github(environ["GITHUB_USER"], environ["GITHUB_PASS"]) + +print("· Get Repository handler") + +if 'GITHUB_REPOSITORY' not in environ: + stdout.flush() + raise(Exception("Repository name not defined! Please set 'GITHUB_REPOSITORY")) + +gh_repo = gh.get_repo(environ['GITHUB_REPOSITORY']) + +print("· Get Release handler") + +tag = getenv('INPUT_TAG', 'tip') + +try: + gh_tag = gh_repo.get_git_ref('tags/%s' % tag) +except Exception as e: + stdout.flush() + # TODO: create the tag/release, instead of raising an exception + raise(Exception("Tag '%s' does not exist!" % tag)) + +gh_release = gh_repo.get_release(tag) + +print("· Upload artifacts") + +artifacts = files + +for asset in gh_release.get_assets(): + print(">", asset) + print(" ", asset.name) + for fname in artifacts: + if asset.name == fname: + print(" removing '%s'..." % asset.name) + asset.delete_asset() + print(" uploading '%s'..." % fname) + gh_release.upload_asset(fname) + artifacts.remove(fname) + break + +for fname in artifacts: + print(" uploading '%s'..." % fname) + gh_release.upload_asset(fname) + +stdout.flush() +print("· Update Release reference (force-push tag)") + +if ('GITHUB_SHA' in environ) and ('GITHUB_REF' in environ) and environ['GITHUB_REF'] != 'refs/tags/%s' % tag: + sha = environ['GITHUB_SHA'] + print("force-push '%s' to %s" % (tag, sha)) + gh_repo.get_git_ref('tags/%s' % tag).edit(sha) + + # TODO: alternatively, update the title/body of the release (while keeping the tag or not) + # gh_release.update_release( + # gh_release.title, + # gh_release.body, + # draft=False, + # prerelease=True, + # tag_name=gh_release.tag_name, + # target_commitish=gh_release.target_commitish + # )