discourse-release: announce published releases on the community forum
A composite action: reads the release from the event file with jq, looks up the repo's Announcements category (release-map.json) and posts as release-bot. One topic per repo+tag via the topic external_id, so a re-run is a no-op. Optional Discord webhook.
This commit is contained in:
1 parent
fab0c4d45e
commit
6245815b09
4 files changed
+142
No files matched your search
@@ -18,3 +18,32 @@ Pin every use by full commit SHA.
|
|||||||
Works in any job image with a POSIX shell. Installs git with apk or apt-get
|
Works in any job image with a POSIX shell. Installs git with apk or apt-get
|
||||||
if the image has none, and clones from the runner's internal Gitea address
|
if the image has none, and clones from the runner's internal Gitea address
|
||||||
(`CI_SERVER_INTERNAL`) so CI traffic never crosses Cloudflare.
|
(`CI_SERVER_INTERNAL`) so CI traffic never crosses Cloudflare.
|
||||||
|
|
||||||
|
## discourse-release
|
||||||
|
|
||||||
|
Announces a published release on the community forum, in the repo's
|
||||||
|
Announcements category. Add to a project repo as `.gitea/workflows/announce.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
jobs:
|
||||||
|
announce:
|
||||||
|
runs-on: light
|
||||||
|
steps:
|
||||||
|
- uses: coffey-labs/actions/discourse-release@<sha>
|
||||||
|
with:
|
||||||
|
api-key: ${{ secrets.DISCOURSE_RELEASE_KEY }}
|
||||||
|
discord-webhook: ${{ secrets.DISCORD_RELEASE_WEBHOOK }} # optional
|
||||||
|
```
|
||||||
|
|
||||||
|
- The repo must be listed in `discourse-release/release-map.json`, the one
|
||||||
|
place that maps repos to forum categories.
|
||||||
|
- `DISCOURSE_RELEASE_KEY` is an org-level secret (coffey-labs and inbuxa)
|
||||||
|
holding release-bot's key, which can only create posts.
|
||||||
|
- Re-running is safe: each repo+tag gets one topic, enforced by the forum.
|
||||||
|
- Release notes are passed to the forum through `jq` only, so quotes,
|
||||||
|
backticks and multi-line Markdown arrive exactly as written.
|
||||||
|
- To turn it off for a repo, delete its `announce.yml`; for everyone, remove
|
||||||
|
the org secret.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: discourse-release
|
||||||
|
description: >-
|
||||||
|
Announce a published Gitea release on the Coffey Labs community forum, in the
|
||||||
|
repo's Announcements category (release-map.json). Safe to re-run: a release
|
||||||
|
is announced at most once.
|
||||||
|
inputs:
|
||||||
|
api-key:
|
||||||
|
description: release-bot's forum API key (scope topics:write only)
|
||||||
|
required: true
|
||||||
|
discord-webhook:
|
||||||
|
description: optional Discord webhook URL; when set, the announcement is also posted there
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
forum:
|
||||||
|
description: forum base URL
|
||||||
|
required: false
|
||||||
|
default: https://community.coffeylabs.org
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- shell: sh
|
||||||
|
env:
|
||||||
|
API_KEY: ${{ inputs.api-key }}
|
||||||
|
DISCORD_WEBHOOK: ${{ inputs.discord-webhook }}
|
||||||
|
FORUM: ${{ inputs.forum }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
EVENT_PATH: ${{ github.event_path }}
|
||||||
|
ACTION_PATH: ${{ github.action_path }}
|
||||||
|
MAP: ${{ github.action_path }}/release-map.json
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
# The job image may be bare; the script needs bash, curl and jq.
|
||||||
|
if ! command -v curl >/dev/null || ! command -v jq >/dev/null || ! command -v bash >/dev/null; then
|
||||||
|
if command -v apk >/dev/null; then apk add --no-cache -q bash curl jq ca-certificates >/dev/null
|
||||||
|
else apt-get update -qq >/dev/null && apt-get install -y -qq --no-install-recommends bash curl jq ca-certificates >/dev/null; fi
|
||||||
|
fi
|
||||||
|
exec bash "$ACTION_PATH/announce.sh"
|
||||||
Executable
+62
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Called by action.yml. Everything user-written (release name, notes) comes in
|
||||||
|
# through the event JSON file and is only ever handled by jq -- never spliced
|
||||||
|
# into a command line or a JSON string by the shell.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
category_path=$(jq -r --arg r "$REPO" '.[$r] // empty' "$MAP")
|
||||||
|
if [ -z "$category_path" ]; then
|
||||||
|
echo "::error::$REPO is not in discourse-release/release-map.json"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tag=$(jq -r '.release.tag_name' "$EVENT_PATH")
|
||||||
|
# The category id, looked up anonymously (the category is public); the
|
||||||
|
# release-bot key is scoped to creating posts and nothing else.
|
||||||
|
category_id=$(curl -fsS "$FORUM/c/$category_path/find_by_slug.json" | jq -r '.category.id')
|
||||||
|
|
||||||
|
# One topic per repo+tag, ever: Discourse enforces external_id uniqueness.
|
||||||
|
external_id="rel-$(printf '%s@%s' "$REPO" "$tag" | sha1sum | cut -c1-40)"
|
||||||
|
|
||||||
|
payload=$(jq -n --arg repo "$REPO" --argjson category "$category_id" --arg ext "$external_id" \
|
||||||
|
--slurpfile ev "$EVENT_PATH" '
|
||||||
|
($ev[0].release) as $r
|
||||||
|
| ($repo | split("/") | last) as $name
|
||||||
|
| ($r.name // "" | if . == "" then $r.tag_name else . end) as $title
|
||||||
|
| {
|
||||||
|
title: "\($name) \($title) released",
|
||||||
|
category: $category,
|
||||||
|
external_id: $ext,
|
||||||
|
raw: (
|
||||||
|
"**\($name) \($r.tag_name)** is out.\n\n"
|
||||||
|
+ (if ($r.body // "") == "" then "" else "\($r.body)\n\n" end)
|
||||||
|
+ "---\n[Release on Gitea](\($r.html_url))"
|
||||||
|
)
|
||||||
|
}')
|
||||||
|
|
||||||
|
status=$(curl -sS -o /tmp/discourse-release.out -w '%{http_code}' -X POST "$FORUM/posts.json" \
|
||||||
|
-H "Api-Key: $API_KEY" -H "Api-Username: release-bot" \
|
||||||
|
-H "Content-Type: application/json" --data-binary "$payload")
|
||||||
|
body=$(cat /tmp/discourse-release.out)
|
||||||
|
case "$status" in
|
||||||
|
200)
|
||||||
|
url="$FORUM/t/$(jq -r '.topic_slug' <<<"$body")/$(jq -r '.topic_id' <<<"$body")"
|
||||||
|
echo "announced: $url" ;;
|
||||||
|
422)
|
||||||
|
if jq -e '.errors | tostring | test("External ID"; "i")' <<<"$body" >/dev/null; then
|
||||||
|
echo "already announced (external_id $external_id); nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "::error::forum refused the post: $body"; exit 1 ;;
|
||||||
|
*)
|
||||||
|
echo "::error::forum returned HTTP $status: $body"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -n "${DISCORD_WEBHOOK:-}" ]; then
|
||||||
|
jq -n --arg u "$url" --slurpfile ev "$EVENT_PATH" --arg repo "$REPO" '
|
||||||
|
($ev[0].release) as $r
|
||||||
|
| {content: ("**\($repo | split("/") | last) \($r.tag_name)** released\n\($u)")[0:1900]}' |
|
||||||
|
curl -fsS -X POST "$DISCORD_WEBHOOK" -H "Content-Type: application/json" --data-binary @- >/dev/null \
|
||||||
|
&& echo "posted to Discord" \
|
||||||
|
|| echo "::warning::Discord webhook failed; the forum post stands"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Gitea repo -> forum Announcements category (parent/child slug). The one place this mapping lives; adding a project is one line here.",
|
||||||
|
"inbuxa/inbuxa-server": "inbuxa/announcements",
|
||||||
|
"inbuxa/ihasmail-inbuxa": "inbuxa/announcements",
|
||||||
|
"inbuxa/inbuxa-admin": "inbuxa/announcements",
|
||||||
|
"inbuxa/inbuxa-installer": "inbuxa/announcements",
|
||||||
|
"coffey-labs/ihasmail": "ihasmail/announcements",
|
||||||
|
"coffey-labs/ihasmail-oneshot": "ihasmail/announcements",
|
||||||
|
"coffey-labs/ihasvpn": "ihasvpn/announcements",
|
||||||
|
"coffey-labs/cairnobs": "cairn-obs/announcements",
|
||||||
|
"coffey-labs/stalwart-migrator": "coffey-labs/announcements",
|
||||||
|
"coffey-labs/SysAdminAutomation": "coffey-labs/announcements",
|
||||||
|
"coffey-labs/ubuntu2mint": "coffey-labs/announcements"
|
||||||
|
}
|
||||||
Reference in new issue
Block a user