releaser: use GitHub CLI by default; remove option 'use-gh-cli'

This commit is contained in:
umarcor
2021-12-20 06:53:20 +01:00
parent e832625624
commit 459faf880a
5 changed files with 110 additions and 225 deletions

View File

@@ -77,7 +77,6 @@ jobs:
uses: ./releaser/composite uses: ./releaser/composite
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
use-gh-cli: true
files: | files: |
artifact-*.txt artifact-*.txt
README.md README.md
@@ -138,7 +137,6 @@ jobs:
uses: ./releaser uses: ./releaser
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
use-gh-cli: true
files: | files: |
artifact-*.txt artifact-*.txt
README.md README.md
@@ -176,5 +174,3 @@ jobs:
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
files: artifacts/** files: artifacts/**

View File

@@ -6,8 +6,8 @@
Combined with a workflow that is executed periodically, **Releaser** allows to provide a fixed release name for users willing 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. 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 Furthermore, when any [semver](https://semver.org) compilant tagged commit is pushed, **Releaser** can create a release
upload assets. and upload assets.
## Context ## Context
@@ -17,13 +17,16 @@ 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.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)) - [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/create-release](https://github.com/actions/create-release)
- [actions/upload-release-asset](https://github.com/actions/upload-release-asset) - [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. 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. 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.
@@ -80,52 +83,19 @@ jobs:
README.md 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/<name>/<repo>/releases/edit/<tag>`.
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 ### Composite Action
The default implementation of **Releaser** is a Container Action. The default implementation of **Releaser** is a Container Action.
Therefore, a pre-built container image is pulled 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`. 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. 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 Both implementations are functionally equivalent from **Releaser**'s point of view; however, the Composite Action allows
to tweak the version of Python by using [actions/setup-python](https://github.com/actions/setup-python) before. users to tweak the version of Python by using [actions/setup-python](https://github.com/actions/setup-python) before.
## Options ## 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) ### token (required)
@@ -133,7 +103,8 @@ Token to make authenticated API calls; can be passed in using `{{ secrets.GITHUB
### files (required) ### 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`. For creating/updating a release without uploading assets, set `files: none`.
@@ -143,25 +114,28 @@ The default tag name for the tip/nightly pre-release is `tip`, but it can be opt
### rm ### 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 ### 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))
### use-gh-cli are considered snapshots; neither a release is created nor assets are uploaded.
In order to work around the reliability issues explained in section *Troubleshooting* above, option *use-gh-cli* allows
using GitHub's official command line tool ([cli/cli](https://github.com/cli/cli)) for uploading/updating assets.
IMPORTANT: Using this option requires the repository to be cloned (preferredly through [actions/checkout](https://github.com/actions/checkout)).
## Advanced/complex use cases ## Advanced/complex use cases
**Releaser** is essentially a very thin 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. 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. 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: For prototyping purposes, the following job might be useful:

View File

@@ -40,10 +40,6 @@ inputs:
description: 'Whether to create releases from any tag or to treat some as snapshots' description: 'Whether to create releases from any tag or to treat some as snapshots'
required: false required: false
default: true default: true
use-gh-cli:
description: 'Whether to use the GitHub CLI for uploading artifacts (requires actions/checkout)'
required: false
default: false
runs: runs:
using: 'docker' using: 'docker'
image: 'docker://ghcr.io/pytooling/releaser' image: 'docker://ghcr.io/pytooling/releaser'

View File

@@ -40,10 +40,6 @@ inputs:
description: 'Whether to create releases from any tag or to treat some as snapshots' description: 'Whether to create releases from any tag or to treat some as snapshots'
required: false required: false
default: true default: true
use-gh-cli:
description: 'Whether to use the GitHub CLI for uploading artifacts (requires actions/checkout)'
required: false
default: false
runs: runs:
using: 'composite' using: 'composite'
steps: steps:
@@ -59,4 +55,3 @@ runs:
INPUT_TAG: ${{ inputs.tag }} INPUT_TAG: ${{ inputs.tag }}
INPUT_RM: ${{ inputs.rm }} INPUT_RM: ${{ inputs.rm }}
INPUT_SNAPSHOTS: ${{ inputs.snapshots }} INPUT_SNAPSHOTS: ${{ inputs.snapshots }}
INPUT_USE-GH-CLI: ${{ inputs.use-gh-cli }}

View File

@@ -33,14 +33,13 @@ from subprocess import check_call
paramTag = getenv("INPUT_TAG", "tip") paramTag = getenv("INPUT_TAG", "tip")
paramFiles = getenv("INPUT_FILES", None).split() paramFiles = getenv("INPUT_FILES", None).split()
paramRM = getenv("INPUT_RM", "false") == "true" paramRM = getenv("INPUT_RM", "false") == "true"
paramSnapshots = getenv("INPUT_SNAPSHOTS", "true").lower() == 'true' paramSnapshots = getenv("INPUT_SNAPSHOTS", "true").lower() == "true"
paramUseGitHubCLI = getenv("INPUT_USE-GH-CLI", "false").lower() == 'true'
paramToken = ( paramToken = (
environ["GITHUB_TOKEN"] environ["GITHUB_TOKEN"]
if "GITHUB_TOKEN" in environ else if "GITHUB_TOKEN" in environ
environ["INPUT_TOKEN"] else environ["INPUT_TOKEN"]
if "INPUT_TOKEN" in environ else if "INPUT_TOKEN" in environ
None else None
) )
paramRepo = getenv("GITHUB_REPOSITORY", None) paramRepo = getenv("GITHUB_REPOSITORY", None)
paramRef = getenv("GITHUB_REF", None) paramRef = getenv("GITHUB_REF", None)
@@ -78,15 +77,10 @@ def GetGitHubAPIHandler(token):
print("· Get GitHub API handler (authenticate)") print("· Get GitHub API handler (authenticate)")
if token is not None: if token is not None:
return Github(token) return Github(token)
raise ( raise (Exception("Need credentials to authenticate! Please, provide 'GITHUB_TOKEN' or 'INPUT_TOKEN'"))
Exception(
"Need credentials to authenticate! Please, provide 'GITHUB_TOKEN' or 'INPUT_TOKEN'"
)
)
def GetReleaseHandler(gh, repo, ref, tag, sha, snapshots): def CheckRefSemVer(gh_ref, tag, snapshots):
def CheckRefSemVer(gh_ref, tag, snapshots):
print("· Check SemVer compliance of the reference/tag") print("· Check SemVer compliance of the reference/tag")
env_tag = None env_tag = None
if gh_ref[0:10] == "refs/tags/": if gh_ref[0:10] == "refs/tags/":
@@ -112,14 +106,16 @@ def GetReleaseHandler(gh, repo, ref, tag, sha, snapshots):
return (tag, env_tag, True) return (tag, env_tag, True)
def GetRepositoryHandler(repo):
def GetRepositoryHandler(gh, repo):
print("· Get Repository handler") print("· Get Repository handler")
if repo is None: if repo is None:
stdout.flush() stdout.flush()
raise (Exception("Repository name not defined! Please set 'GITHUB_REPOSITORY")) raise (Exception("Repository name not defined! Please set 'GITHUB_REPOSITORY"))
return gh.get_repo(repo) return gh.get_repo(repo)
def GetOrCreateRelease(gh_repo, tag, sha, is_prerelease):
def GetOrCreateRelease(gh_repo, tag, sha, is_prerelease):
print("· Get Release handler") print("· Get Release handler")
gh_tag = None gh_tag = None
try: try:
@@ -146,78 +142,6 @@ def GetReleaseHandler(gh, repo, ref, tag, sha, snapshots):
except Exception: except Exception:
raise (Exception(err_msg)) raise (Exception(err_msg))
[tag, env_tag, is_prerelease] = CheckRefSemVer(ref, tag, snapshots)
gh_repo = GetRepositoryHandler(repo)
[gh_release, is_draft] = GetOrCreateRelease(gh_repo, tag, sha, is_prerelease)
return (gh_repo, gh_release, tag, env_tag, is_prerelease, is_draft)
def UploadArtifacts(gh_release, artifacts, remove, token, UseGitHubCLI):
print("· Cleanup and/or upload artifacts")
assets = gh_release.get_assets()
def delete_all_assets(assets):
print("· RM set. All previous assets are being cleared...")
for asset in assets:
print(f" - {asset.name}")
asset.delete_asset()
def delete_asset_by_name(name):
for asset in assets:
if asset.name == name:
asset.delete_asset()
return
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 remove:
delete_all_assets(assets)
else:
if not UseGitHubCLI:
for asset in assets:
replace_asset(artifacts, asset)
if UseGitHubCLI:
env = environ.copy()
env["GITHUB_TOKEN"] = token
cmd = ["gh", "release", "upload", "--clobber", tag] + artifacts
print(f" > {' '.join(cmd)}")
check_call(cmd, env=env)
else:
for artifact in artifacts:
print(f" > {artifact!s}:\n - uploading...")
gh_release.upload_asset(artifact)
def UpdateReference(gh_release, tag, sha, is_prerelease, is_draft): def UpdateReference(gh_release, tag, sha, is_prerelease, is_draft):
print("· Update Release reference (force-push tag)") print("· Update Release reference (force-push tag)")
@@ -240,27 +164,27 @@ def UpdateReference(gh_release, tag, sha, is_prerelease, is_draft):
files = GetListOfArtifacts(sys_argv, paramFiles) files = GetListOfArtifacts(sys_argv, paramFiles)
[gh_repo, gh_release, tag, env_tag, is_prerelease, is_draft] = GetReleaseHandler(
GetGitHubAPIHandler(paramToken),
paramRepo,
paramRef,
paramTag,
paramSHA,
paramSnapshots
)
stdout.flush() stdout.flush()
UploadArtifacts( [tag, env_tag, is_prerelease] = CheckRefSemVer(paramRef, paramTag, paramSnapshots)
gh_release,
files,
paramRM,
paramToken,
paramUseGitHubCLI
)
stdout.flush() stdout.flush()
UpdateReference( gh_repo = GetRepositoryHandler(GetGitHubAPIHandler(paramToken), paramRepo)
gh_release, stdout.flush()
tag, [gh_release, is_draft] = GetOrCreateRelease(gh_repo, tag, paramSHA, is_prerelease)
paramSHA if env_tag is None else None, stdout.flush()
is_prerelease,
is_draft if paramRM:
) print("· RM set. All previous assets are being cleared...")
for asset in gh_release.get_assets():
print(f" - {asset.name}")
asset.delete_asset()
stdout.flush()
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()
UpdateReference(gh_release, tag, paramSHA if env_tag is None else None, is_prerelease, is_draft)