Files

195 lines
7.5 KiB
YAML

---
name: Update RPM Packages
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
inputs:
package:
description: "Single package to update (blank = all)"
required: false
type: string
force_rebuild:
description: "Bump the Release even with no new upstream version"
required: false
type: boolean
default: false
force_refresh:
description: "Re-download the source and rebuild even when nothing changed"
required: false
type: boolean
default: false
no_git_push:
description: "Dry run: build, but neither upload nor push"
required: false
type: boolean
default: false
defaults:
run:
shell: bash
jobs:
# Resolves the package list for the update matrix below, so adding or
# dropping a package is a variable edit on the org, not a commit here.
# See README "Package list".
plan:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.resolve.outputs.packages }}
steps:
- name: Resolve the package list
id: resolve
# Both go through the environment, never pasted into the script: the
# variable is free-form multi-line text.
env:
UPDATE_PACKAGES: ${{ vars.UPDATE_PACKAGES }}
PACKAGE_INPUT: ${{ inputs.package }}
run: |
set -euo pipefail
export LC_ALL=C
: "${UPDATE_PACKAGES:=}" "${PACKAGE_INPUT:=}"
re='^[A-Za-z0-9][A-Za-z0-9._+-]*$'
pkgs=()
declare -A seen=()
# add NAME: validate, then append unless already listed.
add() {
if [[ ! $1 =~ $re ]]; then
echo "::error::${from}: invalid package name '$1' (letters, digits and . _ + - only, starting with a letter or digit)" >&2
exit 1
fi
if [ -z "${seen[$1]:-}" ]; then
seen[$1]=1
pkgs+=("$1")
fi
}
if [ -n "${PACKAGE_INPUT//[[:space:]]/}" ]; then
# workflow_dispatch with a package name runs only that one,
# whatever UPDATE_PACKAGES says. Surrounding whitespace is
# trimmed; the rest must be a single valid name.
from="workflow_dispatch input 'package'"
want=${PACKAGE_INPUT#"${PACKAGE_INPUT%%[![:space:]]*}"}
want=${want%"${want##*[![:space:]]}"}
add "${want}"
else
# "- a\n- b", "a\nb", "- a - b" and "a b" are the same list:
# comments go first, then every whitespace run (CR included, in
# case the value was saved with CRLF line ends) becomes a line
# break. Each line is then a name, a lone "-" list marker, or a
# "-" glued to a name.
from="UPDATE_PACKAGES"
while IFS= read -r tok; do
case ${tok} in
"" | -) continue ;;
-*) tok=${tok#-} ;;
esac
add "${tok}"
done <<< "$(printf '%s\n' "${UPDATE_PACKAGES}" | sed -e 's/#.*$//' | tr -s '[:space:]' '\n')"
fi
# Fail rather than run nothing: an empty matrix would pass and look
# exactly like a night with nothing to update.
if [ "${#pkgs[@]}" -eq 0 ]; then
echo '::error::UPDATE_PACKAGES is not set: add an org or repo variable with one package per line (leading "- " optional)' >&2
exit 1
fi
# Names are validated above, so none needs JSON escaping.
json=
for p in "${pkgs[@]}"; do
json+="${json:+,}\"${p}\""
done
json="[${json}]"
echo "${#pkgs[@]} package(s) from ${from}:"
printf ' - %s\n' "${pkgs[@]}"
echo "matrix: ${json}"
echo "packages=${json}" >> "${GITHUB_OUTPUT}"
update:
needs: plan
runs-on: ubuntu-latest
# rpmbuild runs as the unprivileged `builder` user inside a Fedora image;
# the spec is built with `dist` undefined, so the noarch package is the
# same for every RPM distribution the registry serves.
#
# Prebuilt by build-image.yaml (see dockerfile) so the toolchain is not
# reinstalled in every job every night.
container:
image: git.bjphoster.com/rpm/autoupdate:latest
credentials:
username: ${{ vars.REGISTRY_USER }}
password: ${{ secrets.GIT_TOKEN }}
strategy:
# One broken package must not cancel the rest of the matrix.
fail-fast: false
matrix:
# One entry per package, resolved by the plan job above from
# vars.UPDATE_PACKAGES or the dispatch input.
package: ${{ fromJSON(needs.plan.outputs.packages) }}
steps:
# No bootstrap step: node, git, jq, curl and the packaging toolchain all
# come from the prebuilt image above. node in particular has to be present
# before this action runs, since actions/checkout is JavaScript and the
# runner executes it with `node` from the image.
- uses: actions/checkout@v4
# The same credential serves git (clone/push of the package repos) and
# the package registry upload, which autoupdate.sh reads back through
# `git credential fill`. GIT_TOKEN therefore needs write:repository AND
# write:package.
- name: Configure git
env:
GIT_NAME: ${{ vars.GIT_NAME }}
GIT_EMAIL: ${{ vars.GIT_EMAIL }}
GIT_HOST: ${{ vars.GIT_HOST || 'git.bjphoster.com' }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
run: |
set -euo pipefail
# Validate HERE rather than letting a missing secret surface as an
# opaque 401 several steps later.
for v in GIT_NAME GIT_EMAIL GIT_TOKEN; do
if [ -z "${!v//[[:space:]]/}" ]; then
echo "::error::${v} is empty or unset (see README: required secrets and variables)" >&2
exit 1
fi
done
git config --global user.name "${GIT_NAME}"
git config --global user.email "${GIT_EMAIL}"
git config --global credential.helper store
echo "https://${GIT_EMAIL//@/%40}:${GIT_TOKEN}@${GIT_HOST}" > ~/.git-credentials
chmod 600 ~/.git-credentials
# The `builder` user is baked into the image; this only hands it the
# workspace and the credentials written by the previous step.
- name: Hand over to the build user
run: |
set -euo pipefail
chown -R builder:builder "${GITHUB_WORKSPACE}"
cp /root/.gitconfig /root/.git-credentials /home/builder/
chown -R builder:builder /home/builder
- name: Update ${{ matrix.package }}
env:
GIT_HOST: ${{ vars.GIT_HOST || 'git.bjphoster.com' }}
# Compared against both `true` and 'true' on purpose. A bare
# `${{ inputs.x && '1' || '' }}` is wrong if the runner hands the
# input over as the STRING "false", which is truthy -- that would
# silently enable the flag on every run.
FORCE_REBUILD: ${{ (inputs.force_rebuild == true || inputs.force_rebuild == 'true') && '1' || '' }}
FORCE_REFRESH: ${{ (inputs.force_refresh == true || inputs.force_refresh == 'true') && '1' || '' }}
NO_GIT_PUSH: ${{ (inputs.no_git_push == true || inputs.no_git_push == 'true') && '1' || '' }}
run: |
set -euo pipefail
runuser -u builder -- env \
GIT_HOST="${GIT_HOST}" \
FORCE_REBUILD="${FORCE_REBUILD}" \
FORCE_REFRESH="${FORCE_REFRESH}" \
NO_GIT_PUSH="${NO_GIT_PUSH}" \
./autoupdate.sh "${{ matrix.package }}"