diff --git a/.github/workflows/TestReleaser.yml b/.github/workflows/TestReleaser.yml index 1e3fe11..35c7d69 100644 --- a/.github/workflows/TestReleaser.yml +++ b/.github/workflows/TestReleaser.yml @@ -39,65 +39,27 @@ env: jobs: - test: + + Image: runs-on: ubuntu-latest + env: + DOCKER_BUILDKIT: 1 steps: - uses: actions/checkout@v2 - - run: echo "Build some tool and generate some (versioned) artifacts" > artifact-$(date -u +"%Y-%m-%dT%H-%M-%SZ").txt + - name: Build container image + run: docker build -t ghcr.io/pytooling/releaser -f releaser/Dockerfile releaser - - name: Single - uses: ./releaser + - name: Push container image + uses: ./with-post-step with: - rm: true - token: ${{ secrets.GITHUB_TOKEN }} - files: artifact-*.txt - - - name: List - uses: ./releaser - with: - token: ${{ secrets.GITHUB_TOKEN }} - files: | - artifact-*.txt - README.md - - - name: Add artifacts/*.txt - run: | - mkdir artifacts - echo "Build some tool and generate some artifacts" > artifacts/artifact.txt - touch artifacts/empty_file.txt - - - name: Single in subdir - uses: ./releaser - with: - token: ${{ secrets.GITHUB_TOKEN }} - files: artifacts/artifact.txt - - - name: Add artifacts/*.md - run: | - echo "releaser hello" > artifacts/hello.md - echo "releaser world" > artifacts/world.md - - - name: Directory wildcard - uses: ./releaser - with: - token: ${{ secrets.GITHUB_TOKEN }} - files: artifacts/* - - - name: Add artifacts/subdir - run: | - mkdir artifacts/subdir - echo "Test recursive glob" > artifacts/subdir/deep_file.txt - - - name: Directory wildcard (recursive) - uses: ./releaser - with: - token: ${{ secrets.GITHUB_TOKEN }} - files: artifacts/** + main: | + echo '${{ github.token }}' | docker login ghcr.io -u GitHub-Actions --password-stdin + docker push ghcr.io/pytooling/releaser + post: docker logout ghcr.io - test-composite: - needs: test + Composite: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -152,3 +114,63 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} files: artifacts/** + + + Test: + needs: + - Image + - Composite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - run: echo "Build some tool and generate some (versioned) artifacts" > artifact-$(date -u +"%Y-%m-%dT%H-%M-%SZ").txt + + - name: Single + uses: ./releaser + with: + rm: true + token: ${{ secrets.GITHUB_TOKEN }} + files: artifact-*.txt + + - name: List + uses: ./releaser + with: + token: ${{ secrets.GITHUB_TOKEN }} + files: | + artifact-*.txt + README.md + + - name: Add artifacts/*.txt + run: | + mkdir artifacts + echo "Build some tool and generate some artifacts" > artifacts/artifact.txt + touch artifacts/empty_file.txt + + - name: Single in subdir + uses: ./releaser + with: + token: ${{ secrets.GITHUB_TOKEN }} + files: artifacts/artifact.txt + + - name: Add artifacts/*.md + run: | + echo "releaser hello" > artifacts/hello.md + echo "releaser world" > artifacts/world.md + + - name: Directory wildcard + uses: ./releaser + with: + token: ${{ secrets.GITHUB_TOKEN }} + files: artifacts/* + + - name: Add artifacts/subdir + run: | + mkdir artifacts/subdir + echo "Test recursive glob" > artifacts/subdir/deep_file.txt + + - name: Directory wildcard (recursive) + uses: ./releaser + with: + token: ${{ secrets.GITHUB_TOKEN }} + files: artifacts/** diff --git a/releaser/Dockerfile b/releaser/Dockerfile index 2290f49..728c35c 100644 --- a/releaser/Dockerfile +++ b/releaser/Dockerfile @@ -1,4 +1,12 @@ FROM python:3.9-slim-bullseye COPY releaser.py /releaser.py -RUN pip install PyGithub --progress-bar off +RUN pip install PyGithub --progress-bar off \ + && apt update -qq \ + && apt install -y curl \ + && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \ + dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | \ + tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && apt update -qq \ + && apt install -y gh CMD ["/releaser.py"] diff --git a/releaser/README.md b/releaser/README.md index 703e65f..4949b53 100644 --- a/releaser/README.md +++ b/releaser/README.md @@ -6,8 +6,8 @@ Combined with a workflow that is executed periodically, **Releaser** allows to provide a fixed release name for users willing to use daily/nightly artifacts of a project. -Furthermore, when any [semver](https://semver.org) compilant tagged commit is pushed, **Releaser** can create a release and -upload assets. +Furthermore, when any [semver](https://semver.org) compilant tagged commit is pushed, **Releaser** can create a release +and upload assets. ## Context @@ -17,16 +17,19 @@ GitHub provides official clients for the GitHub API through [github.com/octokit] - [octokit.rb](https://github.com/octokit/octokit.rb) ([octokit.github.io/octokit.rb](http://octokit.github.io/octokit.rb)) - [octokit.net](https://github.com/octokit/octokit.net) ([octokitnet.rtfd.io](https://octokitnet.rtfd.io)) -When GitHub Actions was released in 2019, two Actions were made available through [github.com/actions](https://github.com/actions) for dealing with GitHub Releases: +When GitHub Actions was released in 2019, two Actions were made available through +[github.com/actions](https://github.com/actions) for dealing with GitHub Releases: - [actions/create-release](https://github.com/actions/create-release) - [actions/upload-release-asset](https://github.com/actions/upload-release-asset) However, those Actions were contributed by an employee in spare time, not officially supported by GitHub. -Therefore, they were unmaintained before GitHub Actions was out of the private beta (see [actions/upload-release-asset#58](https://github.com/actions/upload-release-asset/issues/58)) and, a year later, archived. +Therefore, they were unmaintained before GitHub Actions was out of the private beta +(see [actions/upload-release-asset#58](https://github.com/actions/upload-release-asset/issues/58)) +and, a year later, archived. Those Actions are based on [actions/toolkit](https://github.com/actions/toolkit)'s hydrated version of octokit.js. -From a practical point of view, [actions/github-script](https://github.com/actions/github-script) is the natural replacement to those Actions, since it allows to use a pre-authenticated octokit.js client along with the workflow run context. +From a practical point of view, [actions/github-script](https://github.com/actions/github-script) is the natural replacement to those Actions, since it allows to use a pre-authenticated *octokit.js* client along with the workflow run context. Still, it requires writing plain JavaScript. Alternatively, there are non-official GitHub API libraries available in other languages (see [docs.github.com: rest/overview/libraries](https://docs.github.com/en/rest/overview/libraries)). @@ -72,7 +75,7 @@ jobs: # 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: pyTooling/Actions/releaser@main + - uses: pyTooling/Actions/releaser@r0 with: token: ${{ secrets.GITHUB_TOKEN }} files: | @@ -80,52 +83,19 @@ jobs: README.md ``` -### Troubleshooting - -GitHub's internal connections seem not to be very stable; as a result, uploading artifacts as assets does produce -failures rather frequently, particularly if large tarballs are to be published. -When failures are produced, some assets are left in a broken state within the release. -**Releaser** tries to handle those cases by first uploading assets with a `tmp.*` name and then renaming them; if an existing -`tmp.*` is found, it is removed and the upload is retried. -Therefore, restarting the **Releaser** job should suffice for "fixing" a failing run. - -Note: - Currently, GitHub Actions does not allow restarting a single job. - That is unfortunate, because **Releaser** is typically used as the last dependent job in the workflows. - Hence, running **Releaser** again requires restarting the whole workflow. - Fortunately, restarting individual jobs is expected to be supported on GitHub Actions in the future. - See [github/roadmap#271](https://github.com/github/roadmap/issues/271) and [actions/runner#432](https://github.com/actions/runner/issues/432). - -If the tip/nightly release generated with **Releaser** is broken, and restarting the run cannot fix it, the recommended -procedure is the following: - -1. Go to `https://github.com///releases/edit/`. -2. Edit the assets to: - - Remove the ones with a warning symbol and/or named starting with `tmp.*`. - - Or, remove all of them. -3. Save the changes (click the `Update release` button) and restart the **Releaser** job in CI. -5. If that does still not work, remove the release and restart the **Releaser** job in CI. - -See also [eine/tip#160](https://github.com/eine/tip/issues/160). - -Note: - If all the assets are removed, or if the release itself is removed, tip/nightly assets won't be available for - users until the workflow is successfully run. - For instance, Action [setup-ghdl-ci](https://github.com/ghdl/setup-ghdl-ci) uses assets from [ghdl/ghdl: releases/tag/nightly](https://github.com/ghdl/ghdl/releases/tag/nightly). - Hence, it is recommended to try removing the conflictive assets only, in order to maximise the availability. - ### Composite Action The default implementation of **Releaser** is a Container Action. -Therefore, in each run, the container image is built before starting the job. +Therefore, a pre-built container image is pulled before starting the job. Alternatively, a Composite Action version is available: `uses: pyTooling/Actions/releaser/composite@main`. The Composite version installs the dependencies on the host (the runner environment), instead of using a container. -Both implementations are functionally equivalent from **Releaser**'s point of view; however, the Composite Action allows users -to tweak the version of Python by using [actions/setup-python](https://github.com/actions/setup-python) before. +Both implementations are functionally equivalent from **Releaser**'s point of view; however, the Composite Action allows +users to tweak the version of Python by using [actions/setup-python](https://github.com/actions/setup-python) before. ## Options -All options can be optionally provided as environment variables: `INPUT_TOKEN`, `INPUT_FILES`, `INPUT_TAG`, `INPUT_RM` and/or `INPUT_SNAPSHOTS`. +All options can be optionally provided as environment variables: `INPUT_TOKEN`, `INPUT_FILES`, `INPUT_TAG`, `INPUT_RM` +and/or `INPUT_SNAPSHOTS`. ### token (required) @@ -133,7 +103,8 @@ Token to make authenticated API calls; can be passed in using `{{ secrets.GITHUB ### files (required) -Either a single filename/pattern or a multi-line list can be provided. All the artifacts are uploaded regardless of the hierarchy. +Either a single filename/pattern or a multi-line list can be provided. All the artifacts are uploaded regardless of the +hierarchy. For creating/updating a release without uploading assets, set `files: none`. @@ -143,18 +114,28 @@ The default tag name for the tip/nightly pre-release is `tip`, but it can be opt ### rm -Set option `rm` to `true` for systematically removing previous artifacts (e.g. old versions). Otherwise (by default), all previours artifacts are preserved or overwritten. +Set option `rm` to `true` for systematically removing previous artifacts (e.g. old versions). +Otherwise (by default), all previours artifacts are preserved or overwritten. + +Note: + If all the assets are removed, or if the release itself is removed, tip/nightly assets won't be available for + users until the workflow is successfully run. + For instance, Action [setup-ghdl-ci](https://github.com/ghdl/setup-ghdl-ci) uses assets from [ghdl/ghdl: releases/tag/nightly](https://github.com/ghdl/ghdl/releases/tag/nightly). + Hence, it is recommended to try removing the conflictive assets only, in order to maximise the availability. ### snapshots -Whether to create releases from any tag or to treat some as snapshots. By default, all the tags with non-empty `prerelease` field (see [semver.org: Is there a suggested regular expression (RegEx) to check a SemVer string?](https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string)) are considered snapshots; neither a release is created nor assets are uploaded. +Whether to create releases from any tag or to treat some as snapshots. +By default, all the tags with non-empty `prerelease` field (see [semver.org: Is there a suggested regular expression (RegEx) to check a SemVer string?](https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string)) +are considered snapshots; neither a release is created nor assets are uploaded. ## Advanced/complex use cases -**Releaser** is essentially a very fine wrapper to use the GitHub Actions context data along with the classes +**Releaser** is essentially a very thin wrapper to use the GitHub Actions context data along with the classes and methods of PyGithub. -Similarly to [actions/github-script](https://github.com/actions/github-script), users with advanced/complex requirements might find it desirable to write their own Python script, instead of using **Releaser**. +Similarly to [actions/github-script](https://github.com/actions/github-script), users with advanced/complex requirements +might find it desirable to write their own Python script, instead of using **Releaser**. In fact, since `shell: python` is supported in GitHub Actions, using Python does *not* require any Action. For prototyping purposes, the following job might be useful: diff --git a/releaser/action.yml b/releaser/action.yml index dc3e9c0..b212e6f 100644 --- a/releaser/action.yml +++ b/releaser/action.yml @@ -42,4 +42,4 @@ inputs: default: true runs: using: 'docker' - image: 'Dockerfile' + image: 'docker://ghcr.io/pytooling/releaser' diff --git a/releaser/releaser.py b/releaser/releaser.py index 880438a..710269e 100755 --- a/releaser/releaser.py +++ b/releaser/releaser.py @@ -22,196 +22,169 @@ # SPDX-License-Identifier: Apache-2.0 # # ==================================================================================================================== # import re -from sys import argv, stdout, exit as sys_exit +from sys import argv as sys_argv, stdout, exit as sys_exit from os import environ, getenv from glob import glob from pathlib import Path from github import Github, GithubException +from subprocess import check_call -print("· Get list of artifacts to be uploaded") -args = [] -files = [] +paramTag = getenv("INPUT_TAG", "tip") +paramFiles = getenv("INPUT_FILES", None).split() +paramRM = getenv("INPUT_RM", "false") == "true" +paramSnapshots = getenv("INPUT_SNAPSHOTS", "true").lower() == "true" +paramToken = ( + environ["GITHUB_TOKEN"] + if "GITHUB_TOKEN" in environ + else environ["INPUT_TOKEN"] + if "INPUT_TOKEN" in environ + else None +) +paramRepo = getenv("GITHUB_REPOSITORY", None) +paramRef = getenv("GITHUB_REF", None) +paramSHA = getenv("GITHUB_SHA", None) -if "INPUT_FILES" in environ: - args = environ["INPUT_FILES"].split() -if len(argv) > 1: - args = args + argv[1:] - -if len(args) == 1 and args[0] == "none": - files = [] - print("! Skipping 'files' because it's set to 'none") -elif len(args) == 0: - stdout.flush() - raise (Exception("Glob patterns need to be provided as positional arguments or through envvar 'INPUT_FILES'!")) -else: - for item in args: - print(f" glob({item!s}):") - for fname in [fname for fname in glob(item, recursive=True) if not Path(fname).is_dir()]: - if Path(fname).stat().st_size == 0: - print(f" - ! Skipping empty file {fname!s}") - continue - print(f" - {fname!s}") - files.append(fname) - - if len(files) < 1: +def GetListOfArtifacts(argv, files): + print("· Get list of artifacts to be uploaded") + args = files if files is not None else [] + if len(argv) > 1: + args += argv[1:] + if len(args) == 1 and args[0].lower() == "none": + print("! Skipping 'files' because it's set to 'none") + return [] + elif len(args) == 0: stdout.flush() - raise (Exception("Empty list of files to upload/update!")) + raise (Exception("Glob patterns need to be provided as positional arguments or through envvar 'INPUT_FILES'!")) + else: + flist = [] + for item in args: + print(f" glob({item!s}):") + for fname in [fname for fname in glob(item, recursive=True) if not Path(fname).is_dir()]: + if Path(fname).stat().st_size == 0: + print(f" - ! Skipping empty file {fname!s}") + continue + print(f" - {fname!s}") + flist.append(fname) + if len(flist) < 1: + stdout.flush() + raise (Exception("Empty list of files to upload/update!")) + return flist -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: +def GetGitHubAPIHandler(token): + print("· Get GitHub API handler (authenticate)") + if token is not None: + return Github(token) + raise (Exception("Need credentials to authenticate! Please, provide 'GITHUB_TOKEN' or 'INPUT_TOKEN'")) + + +def CheckRefSemVer(gh_ref, tag, snapshots): + print("· Check SemVer compliance of the reference/tag") + env_tag = None + if gh_ref[0:10] == "refs/tags/": + env_tag = gh_ref[10:] + if env_tag != tag: + rexp = r"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" + semver = re.search(rexp, env_tag) + if semver == None and env_tag[0] == "v": + semver = re.search(rexp, env_tag[1:]) + tag = env_tag + if semver == None: + print(f"! Could not get semver from {gh_ref!s}") + print(f"! Treat tag '{tag!s}' as a release") + return (tag, env_tag, False) + else: + if semver.group("prerelease") is None: + # is a regular semver compilant tag + return (tag, env_tag, False) + elif snapshots: + # is semver compilant prerelease tag, thus a snapshot (we skip it) + print("! Skipping snapshot prerelease") + sys_exit() + + return (tag, env_tag, True) + + +def GetRepositoryHandler(gh, repo): + print("· Get Repository handler") + if repo is None: stdout.flush() - raise ( - Exception( - "Need credentials to authenticate! Please, provide 'GITHUB_TOKEN', 'INPUT_TOKEN', or 'GITHUB_USER' and 'GITHUB_PASS'" + raise (Exception("Repository name not defined! Please set 'GITHUB_REPOSITORY")) + return gh.get_repo(repo) + + +def GetOrCreateRelease(gh_repo, tag, sha, is_prerelease): + print("· Get Release handler") + gh_tag = None + try: + gh_tag = gh_repo.get_git_ref(f"tags/{tag!s}") + except Exception: + stdout.flush() + + if gh_tag: + try: + return (gh_repo.get_release(tag), False) + except Exception: + return (gh_repo.create_git_release(tag, tag, "", draft=True, prerelease=is_prerelease), True) + else: + err_msg = f"Tag/release '{tag!s}' does not exist and could not create it!" + if sha is None: + raise (Exception(err_msg)) + try: + return ( + gh_repo.create_git_tag_and_release( + tag, "", tag, "", sha, "commit", draft=True, prerelease=is_prerelease + ), + True, ) + except Exception: + raise (Exception(err_msg)) + + +def UpdateReference(gh_release, tag, sha, is_prerelease, is_draft): + print("· Update Release reference (force-push tag)") + + if is_draft: + # Unfortunately, it seems not possible to update fields 'created_at' or 'published_at'. + print(" > Update (pre-)release") + gh_release.update_release( + gh_release.title, + "" if gh_release.body is None else gh_release.body, + draft=False, + prerelease=is_prerelease, + tag_name=gh_release.tag_name, + target_commitish=gh_release.target_commitish, ) - 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") - -env_tag = None -gh_ref = environ["GITHUB_REF"] -is_prerelease = True -is_draft = False - -if gh_ref[0:10] == "refs/tags/": - env_tag = gh_ref[10:] - if env_tag != tag: - rexp = r"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" - semver = re.search(rexp, env_tag) - if semver == None and env_tag[0] == "v": - semver = re.search(rexp, env_tag[1:]) - tag = env_tag - if semver == None: - print(f"! Could not get semver from {gh_ref!s}") - print(f"! Treat tag '{tag!s}' as a release") - is_prerelease = False - else: - if semver.group("prerelease") is None: - # is a regular semver compilant tag - is_prerelease = False - elif getenv("INPUT_SNAPSHOTS", "true") == "true": - # is semver compilant prerelease tag, thus a snapshot (we skip it) - print("! Skipping snapshot prerelease") - sys_exit() - -gh_tag = None -try: - gh_tag = gh_repo.get_git_ref(f"tags/{tag!s}") -except Exception: - stdout.flush() - -if gh_tag: - try: - gh_release = gh_repo.get_release(tag) - except Exception: - gh_release = gh_repo.create_git_release(tag, tag, "", draft=True, prerelease=is_prerelease) - is_draft = True -else: - err_msg = f"Tag/release '{tag!s}' does not exist and could not create it!" - if "GITHUB_SHA" not in environ: - raise (Exception(err_msg)) - try: - gh_release = gh_repo.create_git_tag_and_release( - tag, "", tag, "", environ["GITHUB_SHA"], "commit", draft=True, prerelease=is_prerelease - ) - is_draft = True - except Exception: - raise (Exception(err_msg)) - -print("· Cleanup and/or upload artifacts") - -artifacts = files - -assets = gh_release.get_assets() + if sha is not None: + print(f" > Force-push '{tag!s}' to {sha!s}") + gh_repo.get_git_ref(f"tags/{tag!s}").edit(sha) -def delete_asset_by_name(name): - for asset in assets: - if asset.name == name: - asset.delete_asset() - return +files = GetListOfArtifacts(sys_argv, paramFiles) +stdout.flush() +[tag, env_tag, is_prerelease] = CheckRefSemVer(paramRef, paramTag, paramSnapshots) +stdout.flush() +gh_repo = GetRepositoryHandler(GetGitHubAPIHandler(paramToken), paramRepo) +stdout.flush() +[gh_release, is_draft] = GetOrCreateRelease(gh_repo, tag, paramSHA, is_prerelease) +stdout.flush() - -def upload_asset(artifact, name): - try: - return gh_release.upload_asset(artifact, name=name) - except GithubException as ex: - if "already_exists" in [err["code"] for err in ex.data["errors"]]: - print(f" - {name} exists already! deleting...") - delete_asset_by_name(name) - else: - print(f" - uploading failed: {ex}") - except Exception as ex: - print(f" - uploading failed: {ex}") - - print(f" - retry uploading {name}...") - return gh_release.upload_asset(artifact, name=name) - - -def replace_asset(artifacts, asset): - print(f" > {asset!s}\n {asset.name!s}:") - for artifact in artifacts: - aname = str(Path(artifact).name) - if asset.name == aname: - print(f" - uploading tmp.{aname!s}...") - new_asset = upload_asset(artifact, name=f"tmp.{aname!s}") - print(f" - removing...{aname!s}") - asset.delete_asset() - print(f" - renaming tmp.{aname!s} to {aname!s}...") - new_asset.update_asset(aname, label=aname) - artifacts.remove(artifact) - return - print(" - keep") - - -if getenv("INPUT_RM", "false") == "true": +if paramRM: print("· RM set. All previous assets are being cleared...") - for asset in assets: + for asset in gh_release.get_assets(): print(f" - {asset.name}") asset.delete_asset() -else: - for asset in assets: - replace_asset(artifacts, asset) - -for artifact in artifacts: - print(f" > {artifact!s}:\n - uploading...") - gh_release.upload_asset(artifact) - stdout.flush() -print("· Update Release reference (force-push tag)") -if is_draft: - # Unfortunately, it seems not possible to update fields 'created_at' or 'published_at'. - print(" > Update (pre-)release") - gh_release.update_release( - gh_release.title, - "" if gh_release.body is None else gh_release.body, - draft=False, - prerelease=is_prerelease, - tag_name=gh_release.tag_name, - target_commitish=gh_release.target_commitish, - ) +print("· Cleanup and/or upload artifacts") +env = environ.copy() +env["GITHUB_TOKEN"] = paramToken +cmd = ["gh", "release", "upload", "--repo", paramRepo, "--clobber", tag] + files +print(f" > {' '.join(cmd)}") +check_call(cmd, env=env) +stdout.flush() -if ("GITHUB_SHA" in environ) and (env_tag is None): - sha = environ["GITHUB_SHA"] - print(f" > Force-push '{tag!s}' to {sha!s}") - gh_repo.get_git_ref(f"tags/{tag!s}").edit(sha) +UpdateReference(gh_release, tag, paramSHA if env_tag is None else None, is_prerelease, is_draft)